This ORM is build over two basic principles :

No lazy loading : The result objects structure is what you've asked in request with dependecies that you requested. Why ? The lazy loading do requests each time you ask access for first time of a dependency. More the database is complex, less performance you will have. And if you have strong batch to launch : I expect you have a strong server to do that !

What you get is what you have asked : The objects dependencies loaded is not the reflect of absolutes relations. If you've selected in where clause a part of the list of a dependency, only this part is loaded and is accessible.

Here is your first DAO :


<?php

namespace Sebk\ProjectBundle\Dao;

use 
Sebk\SmallOrmBundle\Dao\AbstractDao;

class 
Project extends AbstractDao {

    
/**
     * Build structure of DAO
     * @throws \Sebk\SmallOrmBundle\Dao\DaoException
     */
    
protected function build() {
        
$this->setDbTableName("project");
        
$this->setModelName("Project");
        
$this->addPrimaryKey("id""id");
        
$this->addField("name""name");
        
$this->addField("branch_prefix""branchPrefix");
        
$this->addField("leader_user_id""leaderId");
        
$this->addToOne("leader", array("leaderId" => "id"), "User""SebkSmallUserBundle");
    }

}


You can note that your DAO always extends AbstractDao. The method "build" is required and must set a few parameters :

  • $this->setDbTableName("project");

    This statement define that your dao concern the table "project" in database.

  • $this->setModelName("Project");

    This is the class name of your corresponding model.

  • $this->addPrimaryKey("id", "id");

    Here is your primary key : first parameter for db name and second parameter for the name of correponding property of your model class.

  • $this->addField("branch_prefix", "branchPrefix");

    Here is a simple field : as for the primary key, first parameter for db name and second parameter for the name of correponding property of your model class.

  • $this->addToOne("leader", array("leaderId" => "id"), "User", "SebkSmallUserBundle");

    This statement define a to one relation.

    The first parameter defined the property that will map the sub object.

    The second parameter is an array to define the foreign keys of relation : The key of array is the from field and the second key is the field of foreign model.

    The Third parameter is the name of destination model

    The last (optionnal) parameter is the bundle of destination model. If ommited, the ORM look in the same bundle.

    Yes you can do requests cross bundle and corss databases in SmallOrm !

Good practice is creating requests in your DAO object :


<?php

namespace Sebk\ProjectBundle\Dao;

use 
Sebk\SmallOrmBundle\Dao\AbstractDao;

class 
Project extends AbstractDao {

    
/**
     * Build structure of DAO
     * @throws \Sebk\SmallOrmBundle\Dao\DaoException
     */
    
protected function build() {
        
$this->setDbTableName("project");
        
$this->setModelName("Project");
        
$this->addPrimaryKey("id""id");
        
$this->addField("name""name");
        
$this->addField("branch_prefix""branchPrefix");
        
$this->addField("leader_user_id""leaderId");
        
$this->addToOne("leader", array("leaderId" => "id"), "User""SebkSmallUserBundle");
    }
    
    
/**
     * List my projects
     * @param mixed $userId
     * @return array
     */
    
public function getMyProjects($userId) {
        
$query $this->createQueryBuilder("project")
                ->
innerJoin("project""leader")->endJoin();
        
$query->where()
                ->
firstCondition($query->getFieldForCondition("leaderId""project"), "="":userId")
                ->
endWhere()
                ->
setParameter("userId"$userId);
        
        return 
$this->getResult($query);
    }

}


  • First step is creating the query builer object

    $this->createQueryBuilder("project")

    This statement create a query on this model aliased by "project". Traducted to sql this initiate this query statement :

    select * from project as project

  • Second step is join models that you want to map in your result objects.

    $query->innerJoin("project", "leader")->endJoin();

    First parameter is model who own the relation (see the addToOne statement of DAO) and the second is the relation name.

    At this step, our sql statement will be :

    select * from project as project inner join user as leader on project.leader_user_id = leader.id

  • We can now add or condition

    First create where object : $whereObject = $query->where();

    We can create our first condition :
    $whereObject->firstCondition($query->getFieldForCondition("leaderId", "project"), "=", ":userId")

    $query->getFieldForCondition("leaderId", "project") return the field object "leaderId" from model aliased by "project".

    Our sql staement is now :

    select * from project as project inner join user as leader on project.leader_user_id = leader.id
    where project.leaderId = :userId

    Just set parameter : $query->setParameter("userId", $userId);

  • Let execute query : $this->getResult($query);

    Who get this result :

        [
            {
                "id": 10,
                "name": "my project 1",
                "branchPrefix": "dev1",
                "leaderId": 3,
                "leader": {
                    "id": 3
                    "nickname": "sebk69"
                }
            },
            {
                "id": 15,
                "name": "my project 2",
                "branchPrefix": "dev2",
                "leaderId": 3,
                "leader": {
                    "id": 3
                    "nickname": "sebk69"
                }
            }
        ]
    
    These result objects are models object and you can access properties and subobjects with getters and setters :
        $name0 = $resultProjects[0]->getName();
        $leader0Nickname = $resultProjects[0]->getLeader()->getNickname();
    
You can generate dao and models files from database.
  • First add table. In our example we will add table "title" from default connection in "SebkSmallMusicMomentBundle" :
        $ bin/console sebk:small-orm:add-table
        Connection [default] ?
        Bundle [SebkSmallMusicMomentBundle] ?
        Database table [all] ? title
    
    Here is generated files :
    In folder "Dao"
    
    <?php
    namespace Sebk\SmallMusicMomentBundle\Dao;

    use 
    Sebk\SmallOrmBundle\Dao\AbstractDao;

    class 
    TitleDao extends AbstractDao
    {
        protected function 
    build()
        {
            
    $this->setDbTableName("title")
                ->
    setModelName("Title")
                ->
    addPrimaryKey("id""id")
                ->
    addField("name""name")
                ->
    addField("path""path")
                ->
    addField("tags""tags")
                ->
    addField("id_library""idLibrary")
                ->
    addField("id_artist""idArtist")
                ->
    addField("id_album""idAlbum")
                ->
    addToOne("titleAlbum", ["id" => "idAlbum"], "Album")
                ->
    addToOne("titleArtist", ["id" => "idArtist"], "Artist")
                ->
    addToOne("titleLibrary", ["id" => "idLibrary"], "Library")
            ;
        }
    }
    And in folder "Model"
    
    <?php
    namespace Sebk\SmallMusicMomentBundle\Model;

    use 
    Sebk\SmallOrmBundle\Dao\Model;

    class 
    TitleModel extends Model
    {
    }
  • After every alter table you can just run :
         $ bin/console sebk:small-orm:update-dao
     
    and the build method for all dao of added tables will be updated.
  • These two commands not alter code outside build method and your own methods are untouched.
Database layers allow you to manage database modifications across versions of your code and between multiples bundles. For example, we have two bundles :
  • SmallMusicMomentBundle
  • SmallUserBundle
The SmallUserBundle don't know SmallMusicMoment bundle and it's define two tables which are used by SmallMusicMomentBundle. The user table and the user_role table. These tables can be altered between versions of SmallMusicMomentBundle. The goal of database layers is to maintain the build of database over versions and bundle without thinking of the work of other contributers. First, we define SmallUserBundle first version layer. Here is the directory structure :


  • We have the "SmallUser-1" folder which define the name of layer.
  • The sql scripts to create tables are in "scripts" directory. They will be executed in alphabetical order.
  • The configuration is in the "config.yml" file.
In this first example, the "config.yml" file contains only the connection in which the scripts will be executed :
connection: default
The second layer is in SmallMusicMomentBundle :


In the "SmallMusicMoment-1" layer, the config file define the dependency of the SmallUserBundle layer :
connection: default
depends:
    - SmallUser-1@SebkSmallUserBundle
This mean that "SmallMusicMoment-1" can't be executed before layer "SmallUser-1" has been executed. Now we can execute layers :
$ bin/console sebk:small-orm:layers-execute
Execute layer SmallUser-1... done
Execute layer SmallMusicMoment-1... done
In addition, you can add selections based on values in 'paramters.yml' to create specific layers on installation :
connection: default
depends:
    - SmallUser-1@SebkSmallUserBundle
required-parameters:
    client_name: "Acme company"
With that configuration, the layer will not be executed until the parameter "client_name" has the value "Acme company".
SmallOrm come with helper class to validate your model. Simply add a class that extends AbstractValidator with a validate method.

Here is an example for model BareCode, the field code must be unique :

<?php

namespace Sebk\SmallMusicMomentBundle\Validator;

use 
Sebk\SmallOrmBundle\Validator\AbstractValidator;

class 
BareCode extends AbstractValidator
{

    
/**
     * Validate
     * @return boolean
     */
    
public function validate()
    {
        
$this->message "";
        
$valid true;

        if (!
$this->testUnique("code")) {
            
$this->message "The bare code must be unique";
            
$valid false;
        }

        return 
$valid;
    }

}
And here is the persist process in your controller or service :

<?php

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use 
Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use 
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use 
Symfony\Component\HttpFoundation\Request;
use 
Symfony\Component\HttpFoundation\Response;

/**
 * @route("/api/barecodes")
 */
class BareCodeController extends Controller
{
    
/**
     * @route("")
     * @method({"POST"})
     */
    
public function saveAction(Request $request)
    {
        
// Get DAO
        
$daoBareCode $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle""BareCode");

        
// Create model from POST data
        
$model $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));

        
// Validate
        
if ($model->getValidator()->validate()) {
            
// And persist
            
$model->persist();
        } else {
            
// Validation failed, send validator messages as response
            
return new Response(json_encode($model->getValidator()->getMessage()), 400);
        }

        
// Successfull, send modified ressource
        
return new Response(json_encode($model));
    }
}
You can use method delete for remove associated record in database :
$model->delete();
You can take snapshot of model with backup method to consult old values later. For example
// Backup model
$user->backup();

// Change user name
$user->setName("New name");

// Test if modified
if($user->getName() != $user->getBackup()->name) {
    echo "The name have been updated";
}
By default, this method create only backup on the model and not on dependencies. To backup also dependencies you can specify true as parameter :
// Backup model deeply
$user->backup(true);

// Change group name
$user->getGroup()->setName("New group name");

// Test if modified
if($user->getGroup()->getName() != $user->getGroup()->getBackup()->name) {
    echo "The group name has been updated";
}

// Or test any modification on model since last backup
if($user->modifiedSinceBackup()) {
    echo "The user has been modified";
}
Notes :
  • The backup is serialised on json_encode
  • The metadata is also saved
It is also possible to rebuild old model :
// Set name of user
$user->setName("Seb");

// Backup model
$user->backup();

// Change name
$user->setName("Opheli");

// Restore backup in $user2
$user2 = $dao->makeModelFromStdClass($user->backup());

// The two models names are ...
var_dump($user->getName()); // Output : Opheli
var_dump($user2->getName()); // Output : Seb
Transactions are usable by small orm from connections. You can :
  • Start transaction
  • Commit
  • Rollback
If the script end without commit, a rollback is done.

Here is an example from our code barre controller :

<?php

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use 
Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use 
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use 
Symfony\Component\HttpFoundation\Request;
use 
Symfony\Component\HttpFoundation\Response;

/**
 * @route("/api/barecodes")
 */
class BareCodeController extends Controller
{
    
/**
     * @route("")
     * @method({"POST"})
     */
    
public function saveAction(Request $request)
    {
        
// Get default connection
        
$connection $this->get("sebk_small_orm_connections")->get("default");

        
// Starting transaction
        
$connection->startTransaction();

        
// Get DAO
        
$daoBareCode $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle""BareCode");

        
// Create model from POST data
        
$model $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));

        
// Validate model
        
if ($model->getValidator()->validate()) {
            try {
                
// Persist and commit
                
$model->persist();
                
$connection->commit();
            } catch (\
Exception $e) {
                
// Rollback if an afterSave method throw an exception
                
$connection->rollback();
            }
        } else {
            
// Validation failed, send validator messages as response
            
return new Response(json_encode($model->getValidator()->getMessage()), 400);
        }

        
// Successfull, send modified resource
        
return new Response(json_encode($model));
    }
}
Here is the code to put in your app/config/config.yml :
sebk_small_orm:
    connections:
        default:
            type: mysql
            host: %db_host%
            database: %database_name%
            encoding: utf8
            user:     %db_user%
            password: %db_password%
    bundles:
        AppAcmeBundle:
            connections:
                default:
                    dao_namespace: App\AcmeBundle\Dao
                    model_namespace: App\AcmeBundle\Model
                    validator_namespace: App\AcmeBundle\Validator