Code Monkey home page Code Monkey logo

moa's Introduction

MOA

Build Status Coverage Status Latest Stable Version License

This project is no longer maintained.

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:

Hierarchy & Responsibilities

Builder

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.

Mother

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.

Hierarchy

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]

API

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,
            ]
        ];
}

Create and Update

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

Delete Object

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

Inflate Object

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".

Getters and Setters

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']);

Extending

Mother

To inject logic between Mother and the generated models:

  1. Extend Gajus\MOA\Mother class.
  2. Build models using --extends property.

Individual models

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.

Triggers

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 () {}

Validation

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 () {}

Naming Convention

  • MOA model names are using CamelCase convention (e.g. UserAgent).
  • Table names must be singular (e.g. user_agent not user_agents) using underscore convention.

Builder Script

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"

Parameters

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.

Installation

MOA uses Composer to install and update:

curl -s http://getcomposer.org/installer | php
php composer.phar require gajus/moa

moa's People

Contributors

gajus avatar mpscholten avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moa's Issues

Object Composition and Collections

Use cases of updates involving object composition seems like it still needs to be handled also a mechanism for updating Collections would be useful as well.

Lazy loading

Correct me if I am wrong but it appears as if all object data is loaded eagerly. Providing an option for lazy loading of data would be incredibly useful.

Setter is assuming unicode encoding

MySQL encoding does not seem to translate directly to PHP character encoding names. Setter is assuming that value is unicode and might produce unexpected results.

if (!is_null(static::$columns[$name]['character_maximum_length']) && static::$columns[$name]['character_maximum_length'] < mb_strlen($value)) {
    throw new Exception\InvalidArgumentException('Property does not conform to the column\'s maxiumum character length limit.');
}

How should afterUpdate method be triggered when saving object does not result in MySQL query?

Suppose that you construct an object, e.g.

<?php
$string = new \Sandbox\Model\String($this->db);
$string['name'] = 'foo';
$string->save(); // INSERTs object to the database and triggers "afterInsert" method
$string['name'] = 'bar';
$string->save(); // UPDATEs object's instance in the database and triggers "afterUpdate" method
$string->save(); // Does not not execute MySQL query because object state has not changed.

Should the 3rd call to save:

  1. Issue MySQL query? (even though it is assumed that it will not affect the record)
  2. If query is not executed, should the afterUpdate method be triggered?

PDO tests factory

need to use Factory class for creating PDO objects.

instead of $this->db = new \PDO('mysql:dbname=moa', 'travis');
need write $this->db = PDOFactory::GetInstance();

and use something like this:

class PDOFactory{
static function GetInstance(){
return new \PDO('mysql:dbname=moa', 'travis');
}
}

And connection credentials will be saved on only one file instead of all tests .

Relation validation

There is an improvement suggestion for Eager loading (#3). In addition, MOA save method should validate that all of the relations (identified using the "_id" attribute) are met.

Support for transactions

Sequelize (http://sequelizejs.com/docs/latest/transactions) transaction implementation is achieved by establishing a new connection to the database.

PDO has transactions. However, leaving transaction handling to the user at the PDO level would create a direct dependency, not documented in MOA documentation. Sequeliser implementation uses a callback. MOA could implement similar approach to contain transactions. Existing implementation following similar pattern is Laravel, http://laravel.com/docs/database#database-transactions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.