MOA (Mother of All) is a database abstraction using Active Record pattern:
Active record is an approach to accessing data in a database. A database table or view is wrapped into a class. Thus, an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database. When an object is updated the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.
– http://en.wikipedia.org/wiki/Active_record_pattern
MOA is designed to handle CRUD operations.
MOA is not ORM. MOA does not work with object relations and dependencies. However, these libraries do:
MOA does not implement elaborate finders, filters or methods for querying data. However, these libraries do:
MOA is using dynamic code generation to represent your database. builder script generates a file for each table using attributes fetched from the database (e.g. column name, type, default value, etc.). These classes are generated dynamically to reduce the amount of hand-coded duplication of the data representation.
This is an example of generated class.
With other Active Record implementations you do not need a generator because these properties are either hand-typed or fetched during the execution of the code. The former is tedious and error-prone, while the latter is a lazy-workaround that has a considerable performance hit.
All models extend Gajus\MOA\Mother
. Mother attempts to reduce the number of executions that would otherwise cause an error only at the time of interacting with the database. This is achieved by using the pre-fetched table attributes to work out when:
- Accessing a non-existing property.
- Setting property that does not pass derived or custom validation logic.
- Saving object without all the required properties.
Furthermore, Mother is keeping track of all the changes made to the object instance. UPDATE
query will include only the properties that have changed since the last synchronization. If object is saved without changes, then UPDATE
query is not executed.
If you know a negative downside of the behavior described above, please contribute a warning.
Delete operation will remove the object reference from the database and unset the primary key property value.
Using MOA you can choose your own namespace) and have your own base class.
This is an example of an application hierarchy incorporating all of the MOA components:
Gajus\MOA\Mother
you base model [optional]
MOA generated models
your hand-typed models [optional]
your hand-typed domain logic [optional]
This section of the documentation is using code examples from a fictional application to introduce you to the API. The My\App\Model\Person
model in the example extends from:
/**
* This class is generated using https://github.com/gajus/moa.
* Do not edit this file; it will be overwritten.
*/
abstract class Person extends \Gajus\MOA\Mother {
const TABLE_NAME = 'person';
const PRIMARY_KEY_NAME = 'id';
static protected
$columns = [
'id' => [
'column_type' => 'int(10) unsigned',
'column_key' => 'PRI',
'column_default' => NULL,
'data_type' => 'int',
'is_nullable' => false,
'extra' => 'auto_increment',
'character_maximum_length' => NULL,
],
'name' => [
'column_type' => 'varchar(100)',
'column_key' => '',
'column_default' => '',
'data_type' => 'varchar',
'is_nullable' => false,
'extra' => '',
'character_maximum_length' => 100,
],
'language' => [
'column_type' => 'varchar(100)',
'column_key' => '',
'column_default' => 'English',
'data_type' => 'varchar',
'is_nullable' => false,
'extra' => '',
'character_maximum_length' => 100,
]
];
}
Object is inserted and updated using save
method. Object is inserted to the database if instance primary key property has no value. Otherwise, object is updated using the primary key property value.
/**
* @param PDO $db
* @param int $id
*/
$person = new \My\App\Model\Person($db);
// Set property
$person['name'] = 'Foo';
// Insert object to the database
$person->save();
# $person['id'] 1
When object is inserted to the database, new object state is fetched from the database:
// Note that "language" property was not set,
// though it had default value in the table schema.
# $person['language'] English
// Update property
$person['name'] = 'Bar';
// Save object state to the database
$person->save();
# $person['id'] 1
Deleting object will remove the associated entry from the database and unset the primary key property value.
$person->delete();
# $person['id'] null
However, other property values are not discarded. If the same object instance is saved again, it will be inserted to the database with new primary key value:
# $person['name'] Bar
$person->save();
# $person['id'] 2
Object is inflated using the primary key:
$person = new \My\App\Model\Person($db, 2);
In the above example, object data is retrieved from the database where primary key value is "2".
MOA implements ArrayAccess
interface. You can manipulate object properties using the array syntax, e.g.
<?php
$person = new \My\App\Model\Person($db);
$person['name'] = 'Baz';
$person->save();
or if you need to set multiple properties at once:
/**
* Shorthand method to pass each array key, value pair to the setter.
*
* @param array $data
* @return Gajus\MOA\Mother
*/
$person->populate(['name' => 'Qux', 'language' => 'Lithuanian']);
To inject logic between Mother and the generated models:
- Extend
Gajus\MOA\Mother
class. - Build models using
--extends
property.
Models generated using MOA are abstract
. You need to extend all models before you can use them:
<?php
namespace My\App\Model;
class Person extends \Dynamically\Generated\Person {
static public function get[Where][..] (\PDO $db) {
$person_id = $db
->query("SELECT `" . static::$properties['primary_key_name'] . "` FROM `" . static::$properties['table_name'] . "` ORDER BY `[..]` DESC LIMIT 1")
->fetch(\PDO::FETCH_COLUMN);
if (!$person_id) {
throw new \Gajus\MOA\Exception\RecordNotFoundException('[..]');
}
return new static::__construct($db, $person_id);
}
static public function getMany[Where][..] (\PDO $db) {
$sth = $db->prepare("SELECT * FROM `" . static::$properties['table_name'] . "` WHERE `[..]` = ?");
$sth->execute(/* [..] */);
return $sth->fetchAll(\PDO::FETCH_ASSOC);
}
}
MOA convention is to prefix "getMany[Where]" methods that return array and "get[Where]" that return an instance of
Mother
. This is not enforced. It is an observation of what works the best in practise.
These methods can interrupt the respective transactions:
/**
* Triggered after INSERT query but before the transaction is committed.
*
* @return void
*/
protected function afterInsert () {}
/**
* Triggered after UPDATE query but before the transaction is committed.
*
* @return void
*/
protected function afterUpdate () {}
/**
* Triggered after DELETE query but before the transaction is committed.
*
* @return void
*/
protected function afterDelete () {}
MOA ensures that user input is compatible with the schema, e.g. if input will be truncated because it is too long.
MOA provides two types of validation that you can implement before the schema validation.
/**
* Triggered when an attempt is made to change object property.
* Returning an error message will discard the transaction and throw Gajus\MOA\Exception\ValidationException exception.
*
* @param string $name
* @param mixed $value
* @return null|string
*/
protected function validateSet ($name, $value) {}
/**
* Triggered when an attempt is made to save object state.
* Returning an error message will discard the transaction and throw Gajus\MOA\Exception\ValidationException exception.
*
* @return null|mixed
*/
protected function validateSave () {}
- MOA model names are using CamelCase convention (e.g.
UserAgent
). - Table names must be singular (e.g.
user_agent
notuser_agents
) using underscore convention.
Models are built using ./bin/build.php
script, e.g. unit testing dependencies in this repository are built using:
php ./bin/build.php\
--namespace "Sandbox\Model\MOA"\
--database "moa"\
--path "./tests/Sandbox/Model/MOA"
Name | Description |
---|---|
path |
[required] Path to the directory where the models will be created. |
database |
[required] MySQL database name. |
host |
MySQL database host. |
user |
MySQL database user. |
password |
MySQL database password. |
namespace |
[required] PHP class namespace; |
extends |
PHP class to extend. Defaults to "\Gajus\MOA\Mother". |
All .php
files will be deleted from the destination path
. The destination path
must have an empty .moa
file. This requirement is a measure to prevent accidental data loss.
MOA uses Composer to install and update:
curl -s http://getcomposer.org/installer | php
php composer.phar require gajus/moa