SQL Mapper

The SQL Object-Relational-Mapper is an implementation of the abstract Active Record Cursor class.

Namespace: \DB\SQL
File location: lib/db/sql/mapper.php


To use the SQL ORM, create a valid SQL DB Connection and follow this example:

$mapper = new \DB\SQL\Mapper(\DB\SQL $db, string $table [, array|string $fields = NULL [, int $ttl = 60 ]] )

The $fields argument allows you to specify only the fields you need to map. $fields is either an array or a list (according to the F3 function split) of the names of columns to include in the mapper. Defaulted to all columns.

The 4th argument $ttl is a TTL to give the schema detector a hint about how often a SHOW COLUMNS call is issued by the mapper. When $ttl != 0, a cache check for previous schema is triggered and if expired or not found, the actual result is saved to the cache backend, provided a CACHE system is activated.

Now, if you'd like to create a model class, you might like to wrap it up:

$f3->set('DB',new DB\SQL('sqlite:db/database.sqlite'));

class User extends \DB\SQL\Mapper {
	public function __construct() {
		parent::__construct( \Base::instance()->get('DB'), 'users' );

$user = new User();
$user->load('id = 1');
// etc.



The $filter argument for SQL accepts the following structure:

  • string value for simple where strings

    string $whereClause
  • array value for parameterized queries

    array ( string $whereClause [, string $bindValue1 [, string $bindValue2 [, ... ]]] )

Parameterized Queries

It is recommended to use parameterized queries for all where conditions that may include user input data.

An example with question mark positional parameters:

$mapper->load(array('username = ? and password = ? and deleted = 0','John','acbd18db4cc2f85cedef654fccc4a4d8'));

And with named parameters:

	'username = :user and password = :pass and deleted = 0',
Workaround: Due to a PDO limitation, you cannot use a named parameter more than once in a query. You need to create e.g. 2 parameters :user1 and :user2 and pass them the same value.
Make Your Choice: You cannot use both named and question mark positional parameter markers within the same SQL statement; pick one or the other parameter style but don't mix.
User-specified data type

Usually the data type is auto-detected, but to force a bind value to be a specific PDO type, use the following syntax:

	'prize > :prize and active = 1',
	':prize' => array(123, \PDO::PARAM_INT)

When you use a LIKE operator in your where condition, notice that the % wildcards do not belong into the where criteria, but goes into the bind parameter like this:

$user->find(array('email LIKE ?','%gmail%')); // returns all users with an email address at GMAIL

Full-text search with MATCH

If you'd like to do a full-text search for a keywords against one or multiple fields in MySQL, you can use the MATCH AGAINST feature, which looks like this:

$text = 'some text';
$mapper->find(["MATCH (name,code) AGAINST (:search IN BOOLEAN MODE)", ':search' => $text ]);

If you also want to sort the results by relevance, you need to add this match expression as adhoc field like this:

$mapper->relevance = "MATCH (name,code) AGAINST (:search1 IN BOOLEAN MODE)";
$mapper->find(["MATCH (name,code) AGAINST (:search2 IN BOOLEAN MODE)", ':search1' => $text, ':search2' => $text ], ['order'=>'relevance desc']);

And in case you want to match against multiple keywords at once, you need to wrap and escape the keywords

$mapper->find(["(MATCH(fieldA,fieldB) AGAINST('(".implode('") ("',$keywords).")' IN BOOLEAN MODE )"]);

or insert multiple placeholders:

$mapper->find(["(MATCH(fieldA,fieldB) AGAINST('(?) (?) (?)' IN BOOLEAN MODE )", $a, $b, $c]);


The $option argument for SQL accepts the following structure:

	'order' => string $orderClause,
	'group' => string $groupClause,
	'limit' => integer $limit,
	'offset' => integer $offset


	'order' => 'score DESC, team_name ASC',
	'group' => 'score, player',
	'limit' => 20,
	'offset' => 0



Return the name of the mapped table

string table()


Return TRUE if a given field is defined

bool exists( string $key )


Return TRUE if any/specified field value has changed

bool changed ( [ string $key = NULL ] )


Assign a given value to a field

scalar set( string $key, scalar $val )

This class takes advantage of the Magic class and ArrayAccess interface. It means you can set and get variables with direct access like this:

$mapper->foo = 'bar';
$mapper['foo'] = 'bar';

Virtual Fields

If you set a new value to an empty / not hydrated mapper, you create a virtual field on it. This way you can add some aggregate functions to your query:

$scores = new Scores();
$scores->sum_score = 'SUM(score)';
$scores->avg_score = 'AVG(score)';
echo $scores->sum_score; // returns the sum of all scores made by player_id
echo $scores->avg_score; // returns the avarage score of that player


Retrieve value of a field

scalar get( string $key )

To get the ID of the last inserted row or the last value from a sequence object, you must use the reserved $key value = '_id':

$lastInsertedID = $mapper->get('_id'); // get the ID of the last inserted row or the last value from a sequence object


Clear value of a field

NULL clear( string $key )


Get the name of the PHP type equivalent of a PDO constant

string type( string $pdo )

Basically, this method converts a given PDO types constants to the equivalent named PHP types as follow:

switch ($pdo) {
		return 'unset';
	case \PDO::PARAM_INT:
		return 'int';
		return 'bool';
	case \PDO::PARAM_STR:
		return 'string';


Cast value to a PHP type

scalar value( string $type, scalar $val );

This method allows you to cast the value from a DB to a PHP type. Basically, this method converts PDO types to equivalent PHP types as follow:

switch ($type) {
		return (unset)$val;
	case \PDO::PARAM_INT:
		return (int)$val;
		return (bool)$val;
	case \PDO::PARAM_STR:
		return (string)$val


Return the fields of the mapper object as an associative array

array cast( [ object $obj = NULL ] );


Build a query string and execute it

array select( string $fields [, string|array $filter = NULL [, array $options = NULL [, int $ttl = 0 ]]] );


Return records that match a given criteria

array find( [ string|array $filter = NULL [, array $options = NULL [, int $ttl = 0 ]]] );


Count records that match a given criteria

int count( [ string|array $filter = NULL [, $ttl=0 ]] )


Insert a new record

object insert()


Update the current record

object update()


Update an existing record, or insert a new one

object save()

If one or more records have been loaded into the mapper, save will use the update method. If there are no records currently loaded in the mapper, save will use the insert method. The save method is often used in conjunction with the copyfrom method.


Return the record at the specified offset using the same criteria as previous load() call and make it active

array skip( [ int $ofs = 1 ] )


Delete the current record

int erase( [ string|array $filter = NULL ] )

This deletes the current mapped record. If a $filter is given, it performs a SQL DELETE FROM $this->table WHERE $filter on the table specified when instanciating the mapper. Notice that erase event hooks are skipped when $filter is present.


Reset the cursor

NULL reset( )

All underlying values are set to NULL.


Hydrate the mapper object using an array

NULL copyfrom( array | string $var [, callback $func = NULL ] )

This function allows you to hydrate the mapper using an array (or the name of a hive variable containing an array).

$func is a callback function you can apply to the hive array variable. As explained in the Databases User Guide, the array keys must have names identical to the mapper object properties. It allows for example to hydrate the mapper object with the fields of a POSTed form:

$f3->get('user')->copyfrom('POST'); // F3 synch the 'POST' hive array variable with the $_POST array

Danger By default, copyfrom takes the whole array provided; in our example above, the whole POST from the <form>. So if somebody modifies or forges your form by adding some extra <input> fields in your DOM with tools like e.g. firebug, it's possible to overwrite e.g. the ID of the record, the permission role, or what ever… Pretty huge security leak. Fortunately, F3 offers you a versatile solution through a callback function you can use to apply any pre-processing on the hive array variable, such as normalizing the values and/or filtering and limiting the fields to copy from. Your callback function will receive the hive array variable and must similarly return an array of keys/values pairs: the fields to pass to the mapper object.

Ok, let's do it. For example, let's use a callback filter function retaining only the fields 'name' & 'age':

$db = new DB\SQL('sqlite:db/ent.sqlite');
$f3->set('user',new DB\SQL\Mapper($db,'users'));
$f3->get('user')->copyfrom('POST',function($val) {
	// the 'POST' array is passed to our callback function
    return array_intersect_key($val, array_flip(array('name','age')));

That's it! As F3 sanitizes the values, with such an extra filtering, your DB is safe from injections.


Populate hive array variable with mapper fields

NULL copyto( string $key )


Return field names

array fields( bool $adhoc = TRUE )

This method returns all available fields for this mapper. The $adhoc argument controls if the adhoc virtual fields are returned as well. This method will respect any limitations on fields that were set during instantiation.


Returns the table schema

array schema( array $fields = null )

When $fields argument is provided, it's used to update the underlaying field schema. See Retrieve schema of SQL table for additional information.


Return TRUE if field is not nullable

bool required( string $field )


Convert an array to a mapper object

protected object factory( array $row )

This protected method is used internally by the select method.