Data source: SQL


1Presentation

If you have correctly configured the database connection parameters, Temma automatically creates an object of type \Temma\Datasources\Sql. By convention, we will assume that you have named this connection db in the etc/temma.php file (see the configuration documentation).

The connection is then available in the controller by writing:

$this->db

In the other objects managed by the dependency injection component, the connection to the database is accessible by writing:

$loader->dataSources['db']

2Configuration

In the etc/temma.php file (see the configuration documentation), you declare the DSN (Data Source Name) used to connect to the database.
The way the DSN is written depends on the type of database involved. For classic database servers, it is written: TYPE://LOGIN:PASSWORD@SERVER[:PORT]/BASE

This applies to the following database types: mysql, mysqli, pgsql, cubrid, sybase, mssql, dblib, firebird, ibm, informix, sqlsrv, oci, odbc, 4D.

For sqlite and sqlite2 databases, the DSN is: sqlite:/chemin/vers/fichier.sq3


3Unified calls

SQL databases can be used in the same way as other data sources, to store key-value pairs, either raw (string storage) or by serializing complex data.

To do this, the database connection must allow access to a table named TemmaData, defined as follows:

CREATE TABLE TemmaData (
    key    CHAR(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
    data   LONGTEXT,
    PRIMARY KEY (key)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

3.1Array-type access

// verification of data existence
if (isset($this->db['key1']))
    doSomething();

// read data (deserialized)
$data = $this->db['key1'];

// data writing (serialized)
$this->db['key1'] = $value;

// data deletion
unset($this->db['key1']);

// elements count
$nbr = count($this->db);

3.2General methods

// verification of data existence
if ($this->db->isSet('user:1'))
    doSomething();

// data deletion
$this->db->remove('user:1');

// multiple data deletion
$this->db->mRemove(['user:1', 'user:2', 'user:3']);

// prefix-based data deletion
$this->db->clear('user:');

// delete all data
$this->db->flush();

3.3Management of complex serialized data

// search for keys matching a prefix
$users = $this->db->search('user:');

// prefix-based key search, with data recovery (deserialized)
$users = $this->db->search('user:', true);

// data reading (deserialized)
$user = $this->db->get('user:1');
// reading data with default value
$color = $this->db->get('color', 'blue');
// read data with data creation if necessary
$user = $this->db->get("user:$userId", function() use ($userId) {
    return $this->dao->get($userId);
});

// read multiple data (deserialized)
$users = $this->db->mGet(['user:1', 'user:2', 'user:3']);

// data writing (serialized)
$this->db->set('user:1', $userData);

// multiple data writing (serialized)
$this->db->mSet([
    'user:1' => $user1data,
    'user:2' => $user2data,
    'user:3' => $user3data,
]);

3.4Raw data management

// search for keys matching a prefix
$colors = $this->db->find('color:');

// prefix-based key search, with (raw) data retrieval
$colors = $this->db->find('color:', true);

// read data (raw)
$html = $this->db->read('page:home');
// read data with default value
$html = $this->db->read('page:home',
                        '<html><body><h1>Homepage</h1><body><html>');
// read data with data creation if necessary
$html = $this->db->read('page:home', function() {
    return file_get_contents('/path/to/homepage.html');
});

// read multiple (raw) data
$pages = $this->db->mRead(['page:home', 'page:admin', 'page:products']);

// copy data to a local file
$this->db->copyFrom('page:home', '/path/to/newpage.html');
// copy data to local file, with default value
$this->db->copyFrom('page:home', '/path/to/newpage.html', $defaultHtml);
// copy data to a local file, with data creation if necessary
$this->db->copyFrom('page:home', '/path/to/newpage.html', function() {
    return file_get_contents('/path/to/oldpage.html');
});

// data writing (raw)
$this->db->write('color:blue', '#0000ff');

// write multiple (raw) data
$this->db->mWrite({
    'color:blue'  => '#0000ff',
    'color:red'   => '#ff0000',
    'color:green' => '#00ff00',
]);

// write (raw) data from a local file
$this->db->copyTo('page:home', '/path/to/homepage.html');

// write multiple (raw) data from local files
$this->db->mCopyTo([
    'page:home'     => '/path/to/homepage.html',
    'page:admin'    => '/path/to/admin.html',
    'page:products' => '/path/to/products.html',
]);

4Specific calls

4.1exec()

exec(string $sql) : void

The exec() method is used to execute queries for which no data is expected in return.
Examples:

// delete an article from its identifier
$this->db->exec("DELETE FROM article WHERE id = '12'");

// update the date of the last identification
// of all the users for whom this date is not defined
$sql = "UPDATE user
        SET lastLogin = NOW()
        WHERE lastLogin IS NULL";
$this->db->exec($sql);

4.2queryOne()

queryOne(string $sql, [string $valueField]) : array

The queryOne() method is used to execute queries for which we want to retrieve a row of data. The information is returned in the form of an associative array, whose keys correspond to the names of the fields in the table.

For example, the following code:

$sql = "SELECT * FROM article WHERE id = '11'";
$data = $this->db->queryOne($sql);
print_r($data);

Will give a result like this:

Array
(
    [id] => 11
    [status] => valid
    [title] => Title of the article
    [creationDate] => 2021-04-04 23:17:39
    [content] => Text of the article...
    [authorId] => 2
)

The second parameter can take the name of one of the fields. In this case, the method will directly return the value of this field.

Example:

$sql = "SELECT COUNT(*) AS nbr FROM article";
$nbr = $this->db->queryOne($sql, 'nbr');

4.3queryAll()

queryAll(string $sql, [string $key], [string $valueField]) : array

The queryAll() method is used to execute queries that return multiple rows of data. The information is retrieved in the form of a list each element of which is an associative array representing a row of data, the keys of which correspond to the names of the fields in the table.

The first parameter contains the SQL query to execute.

For example, the following code:

$sql = "SELECT * FROM article WHERE authorId = '2'";
$data = $this->db->queryAll($sql);
print_r($data);

Will give a result like this:

Array
(
    [0] => Array
        (
            [id] => 11
            [status] => valid
            [title] => Title of the article
            [creationDate] => 2011-04-04 23:17:39
            [content] => Text of the article...
            [authorId] => 2
        )

    [1] => Array
        (
            [id] => 12
            [status] => valid
            [title] => Another title
            [creationDate] => 2011-04-05 20:34:20
            [content] => Another text...
            [authorId] => 2
        )

    [2] => Array
        (
            [id] => 16
            [status] => waiting
            [title] => Third article
            [creationDate] => 2011-04-10 11:03:44
            [content] => Some text...
            [authorId] => 2
        )
)

The second parameter ($key) contains the name of the field to use to index the items in the list.

For example, the following code:

$sql = "SELECT * FROM article WHERE authorId = '2'";
$data = $this->db->queryAll($sql, 'id');
print_r($data);

Will give a result like this:

Array
(
    [11] => Array
        (
            [id] => 11
            [status] => valid
            [title] => Title of the article
            [creationDate] => 2011-04-04 23:17:39
            [content] => Text of the article...
            [authorId] => 2
        )

    [12] => Array
        (
            [id] => 12
            [status] => valid
            [title] => Another title
            [creationDate] => 2011-04-05 20:34:20
            [content] => Another text...
            [authorId] => 2
        )

    [16] => Array
        (
            [id] => 16
            [status] => waiting
            [title] => Third article
            [creationDate] => 2011-04-10 11:03:44
            [content] => Some text...
            [authorId] => 2
        )
)

The third parameter ($valueField) can take the name of one of the fields, which will be used to supply a single value.

For example, the following code:

$sql = "SELECT * FROM article WHERE authorId = '2'";
$data = $this->db->queryAll($sql, 'id', 'title');
print_r($data);

Will give a result like this:

Array
(
    [11] => Title of the article
    [12] => Another title
    [16] => Third article
)

4.4lastInsertId()

lastInsertId(void) : int|string

The lastInsertId() method returns the primary key of the last record created on the current connection to the database.

Example of use:

// add an article to the database
$sql = "INSERT INTO article
        SET title = 'Titre', content = 'Texte', creationDate = NOW()";
$this->db->exec($sql);

// get the identifier of the new article
$id = $db->lastInsertId();

5Escaping special characters

When writing SQL queries, be aware of the risk of SQL injection. To deal with it, you have to escape the special characters.

To do this, the \Temma\Datasources\Sql object offers the quote() and quoteNull() methods.

These two methods take a text as a parameter, and return it after escaping each of its special characters. They add apostrophes at the beginning and at the end of the escaped text.

The quoteNull() method returns the string NULL (without apostrophes) if it has been given an empty string, a number equal to zero, or an empty array.

Examples:

$str = "You're the one";
$sql = "INSERT INTO article SET text = " . $db->quote($str);
// INSERT INTO ARTICLE SET text = 'You\'re the one'

$str = '';
$sql = "INSERT INTO article SET text = " . $db->quote($str);
// INSERT INTO article SET text = ''

$str = '';
$sql = "INSERT INTO article SET text = " . $db->quoteNull($str);
// INSERT INTO article SET text = NULL

6Prepared requests

The \Temma\Datasources\Sql object allows you to play queries prepared in advance, modifying only the parameters used at each execution.

The parameters can be provided in the form of a list, the elements of which replace the question marks placed in the query:

// prepare the request
$sql = "DELETE FROM article WHERE status = ? AND title != ?";
$statement = $db->prepare($sql);
// first execution
$statement->exec(['invalid', 'This one is good']);
// second execution
$statement->exec(['waiting', 'This one too']);

The parameters can be named in the query, and provided as an associative array:

// prepare the request
$sql = "DELETE FROM article WHERE status = :status AND title != :title";
$statement = $db->prepare($sql);
// first execution
$statement->exec([
    ':status' => 'invalid',
    ':title' => 'This one is good',
]);
// second execution
$statement->exec([
    ':status' => 'waiting',
    ':title' => 'This one too',
]);

In both cases, the parameters are automatically escaped (see methods quote() and quoteNull()).

It is also possible to use prepared queries to retrieve data. The queryOne() and queryAll() methods are used to retrieve a row of data and all the rows of data, respectively. The first returns an associative array whose keys are the column names; the second returns a list of associative arrays.
As with the exec() method, these methods can take a list of parameters, or an associative array of parameters.

// prepare the request
$sql = "SELECT * FROM article WHERE id = ?";
$statement = $db->prepare($sql);
// first read
$article1 = $statement->queryOne([12]);
// second read
$article2 = $statement->queryOne([23]);

// prepare the request
$sql = "SELECT * FROM article WHERE status = :status";
$statement = $db->prepare($sql);
// read
$articles = $statement->queryAll([':status' => 'invalid']);

7Transactions management

The \Temma\Datasources\Sql object provides two different ways to manage transactions.

Through an anonymous function, passed as a parameter to the transaction() method. If an exception is thrown in the anonymous function, an automatic rollback is performed; otherwise a commit is performed automatically at the end of the execution of the anonymous function.

// we create the transaction
// the anonymous function receives the connection
// to the database as a parameter
$this->db->transaction(function($db) use ($userId) {
    // we perform various requests
    $db->exec(...);
    $db->exec(...);
    $db->exec(...);
});

Or explicitly, using the startTransaction(), commit() and rollback() methods:

// we open a transaction
$this->db->startTransaction();

// we perform various requests
$this->db->exec(...);
$this->db->exec(...);
$this->db->exec(...);

// conditional processing
if ($something) {
    // the transaction is accepted
    $db->commit();
} else {
    // the transaction is canceled
    $db->rollback();
}