Difference between revisions of "SmallOrmBundle"

From small iceberg
Jump to navigation Jump to search
 
(14 intermediate revisions by the same user not shown)
Line 4: Line 4:
  
 
The goal of this ORM is to access data with better performances than lasy loading.
 
The goal of this ORM is to access data with better performances than lasy loading.
 +
 +
== Repository ==
 +
 +
The source code is available on githut : [https://github.com/sebk69/SebkSmallOrmBundle https://github.com/sebk69/SebkSmallOrmBundle]
  
 
== Basic Principles ==
 
== Basic Principles ==
Line 233: Line 237:
 
      }
 
      }
 
</nowiki>
 
</nowiki>
     
 
 
    After every alter table you can just run :
 
 <nowiki>$ bin/console sebk:small-orm:update-dao</nowiki>
 
 
     
 
    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 ==
 
== Database layers ==
Line 248: Line 243:
  
 
For example, we have two bundles :
 
For example, we have two bundles :
 +
 
    • SmallMusicMomentBundle
 
    • SmallMusicMomentBundle
 +
 
    • SmallUserBundle
 
    • SmallUserBundle
  
Line 312: Line 309:
 
class BareCode extends AbstractValidator
 
class BareCode extends AbstractValidator
 
{
 
{
 
 
    /**
 
    /**
 
 
     * Validate
 
     * Validate
 
 
     * @return boolean
 
     * @return boolean
 
 
     */
 
     */
  
 
    public function validate()
 
    public function validate()
 
 
    {
 
    {
 
 
        $this->message = "";
 
        $this->message = "";
 
 
        $valid = true;
 
        $valid = true;
 
 
        if (!$this->testUnique("code")) {
 
        if (!$this->testUnique("code")) {
 
 
            $this->message = "The bare code must be unique";
 
            $this->message = "The bare code must be unique";
  
Line 338: Line 326:
  
 
        return $valid;
 
        return $valid;
 
 
    }
 
    }
 
 
}
 
}
 +
</nowiki>
  
 
And here is the persist process in your controller or service :
 
And here is the persist process in your controller or service :
  
 +
<nowiki>
 
<?php
 
<?php
  
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 
 
use Symfony\Component\HttpFoundation\Request;
 
use Symfony\Component\HttpFoundation\Request;
 
 
use Symfony\Component\HttpFoundation\Response;
 
use Symfony\Component\HttpFoundation\Response;
  
 
/**
 
/**
 
+
* @route("/api/barecodes")
* @route("/api/barecodes")
+
*/
 
 
*/
 
  
 
class BareCodeController extends Controller
 
class BareCodeController extends Controller
 
 
{
 
{
 
 
    /**
 
    /**
 
 
     * @route("")
 
     * @route("")
 
 
     * @method({"POST"})
 
     * @method({"POST"})
 
 
     */
 
     */
 
 
    public function saveAction(Request $request)
 
    public function saveAction(Request $request)
 
 
    {
 
    {
 
 
        // Get DAO
 
        // Get DAO
 
 
        $daoBareCode = $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle", "BareCode");
 
        $daoBareCode = $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle", "BareCode");
  
 
        // Create model from POST data
 
        // Create model from POST data
 
 
        $model = $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));
 
        $model = $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));
  
 
        // Validate
 
        // Validate
 
 
        if ($model->getValidator()->validate()) {
 
        if ($model->getValidator()->validate()) {
 
 
            // And persist
 
            // And persist
 
 
            $model->persist();
 
            $model->persist();
 
 
        } else {
 
        } else {
 
 
            // Validation failed, send validator messages as response
 
            // Validation failed, send validator messages as response
 
 
            return new Response(json_encode($model->getValidator()->getMessage()), 400);
 
            return new Response(json_encode($model->getValidator()->getMessage()), 400);
 
 
        }
 
        }
 
+
 
        // Successfull, send modified ressource
+
      // Successfull, send modified ressource
 
 
 
        return new Response(json_encode($model));
 
        return new Response(json_encode($model));
 
 
    }
 
    }
 
 
}
 
}
 +
</nowiki>
  
> Delete a model
+
== Delete a model ==
  
 
You can use method delete for remove associated record in database :
 
You can use method delete for remove associated record in database :
  
 +
<nowiki>
 
$model->delete();
 
$model->delete();
 +
</nowiki>
  
> Backup model
+
== Backup model ==
  
 
You can take snapshot of model with backup method to consult old values later. For example :
 
You can take snapshot of model with backup method to consult old values later. For example :
  
 +
<nowiki>
 
// Backup model
 
// Backup model
 
 
$user->backup();
 
$user->backup();
  
 
// Change user name
 
// Change user name
 
 
$user->setName("New name");
 
$user->setName("New name");
  
 
// Test if modified
 
// Test if modified
 
 
if($user->getName() != $user->getBackup()->name) {
 
if($user->getName() != $user->getBackup()->name) {
 
 
    echo "The name have been updated";
 
    echo "The name have been updated";
 
 
}
 
}
 +
</nowiki>
  
 
By default, this method create only backup on the model and not on dependencies. To backup also dependencies you can specify true as parameter :
 
By default, this method create only backup on the model and not on dependencies. To backup also dependencies you can specify true as parameter :
  
 +
<nowiki>
 
// Backup model deeply
 
// Backup model deeply
 
 
$user->backup(true);
 
$user->backup(true);
  
 
// Change group name
 
// Change group name
 
 
$user->getGroup()->setName("New group name");
 
$user->getGroup()->setName("New group name");
  
 
// Test if modified
 
// Test if modified
 
 
if($user->getGroup()->getName() != $user->getGroup()->getBackup()->name) {
 
if($user->getGroup()->getName() != $user->getGroup()->getBackup()->name) {
 
 
    echo "The group name has been updated";
 
    echo "The group name has been updated";
 
 
}
 
}
  
 
// Or test any modification on model since last backup
 
// Or test any modification on model since last backup
 
 
if($user->modifiedSinceBackup()) {
 
if($user->modifiedSinceBackup()) {
 
 
    echo "The user has been modified";
 
    echo "The user has been modified";
 
 
}
 
}
 +
</nowiki>
  
 
Notes :
 
Notes :
Line 471: Line 427:
 
It is also possible to rebuild old model :
 
It is also possible to rebuild old model :
  
 +
<nowiki>
 
// Set name of user
 
// Set name of user
 
 
$user->setName("Seb");
 
$user->setName("Seb");
  
 
// Backup model
 
// Backup model
 
 
$user->backup();
 
$user->backup();
  
 
// Change name
 
// Change name
 
 
$user->setName("Opheli");
 
$user->setName("Opheli");
  
 
// Restore backup in $user2
 
// Restore backup in $user2
 
 
$user2 = $dao->makeModelFromStdClass($user->getBackup());
 
$user2 = $dao->makeModelFromStdClass($user->getBackup());
  
 
// The two models names are ...
 
// The two models names are ...
 
 
var_dump($user->getName()); // Output : Opheli
 
var_dump($user->getName()); // Output : Opheli
 
 
var_dump($user2->getName()); // Output : Seb
 
var_dump($user2->getName()); // Output : Seb
 +
</nowiki>
  
> Transactions
+
== Transactions ==
  
 
Transactions are usable by small orm from connections. You can :
 
Transactions are usable by small orm from connections. You can :
Line 507: Line 459:
 
Here is an example from our code barre controller :
 
Here is an example from our code barre controller :
  
 +
<nowiki>
 
<?php
 
<?php
  
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 
 
use Symfony\Component\HttpFoundation\Request;
 
use Symfony\Component\HttpFoundation\Request;
 
 
use Symfony\Component\HttpFoundation\Response;
 
use Symfony\Component\HttpFoundation\Response;
  
 
/**
 
/**
 
+
* @route("/api/barecodes")
* @route("/api/barecodes")
+
*/
 
 
*/
 
  
 
class BareCodeController extends Controller
 
class BareCodeController extends Controller
 
 
{
 
{
 
 
    /**
 
    /**
 
 
     * @route("")
 
     * @route("")
 
 
     * @method({"POST"})
 
     * @method({"POST"})
 
 
     */
 
     */
 
 
    public function saveAction(Request $request)
 
    public function saveAction(Request $request)
 
 
    {
 
    {
 
 
        // Get default connection
 
        // Get default connection
 
 
        $connection = $this->get("sebk_small_orm_connections")->get("default");
 
        $connection = $this->get("sebk_small_orm_connections")->get("default");
  
 
        // Starting transaction
 
        // Starting transaction
 
 
        $connection->startTransaction();
 
        $connection->startTransaction();
  
 
        // Get DAO
 
        // Get DAO
 
 
        $daoBareCode = $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle", "BareCode");
 
        $daoBareCode = $this->get("sebk_small_orm_dao")->get("SmallMusicMomentBundle", "BareCode");
  
 
        // Create model from POST data
 
        // Create model from POST data
 
 
        $model = $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));
 
        $model = $daoBareCode->makeModelFromStdClass(json_decode($request->getContent()));
  
 
        // Validate model
 
        // Validate model
 
 
        if ($model->getValidator()->validate()) {
 
        if ($model->getValidator()->validate()) {
 
 
            try {
 
            try {
 
 
                // Persist and commit
 
                // Persist and commit
 
 
                $model->persist();
 
                $model->persist();
 
 
                $connection->commit();
 
                $connection->commit();
 
 
            } catch (\Exception $e) {
 
            } catch (\Exception $e) {
 
 
                // Rollback if an afterSave method throw an exception
 
                // Rollback if an afterSave method throw an exception
 
 
                $connection->rollback();
 
                $connection->rollback();
 
 
            }
 
            }
 
 
        } else {
 
        } else {
 
 
            // Validation failed, send validator messages as response
 
            // Validation failed, send validator messages as response
 
 
            return new Response(json_encode($model->getValidator()->getMessage()), 400);
 
            return new Response(json_encode($model->getValidator()->getMessage()), 400);
 
 
        }
 
        }
  
 
        // Successfull, send modified resource
 
        // Successfull, send modified resource
 
 
        return new Response(json_encode($model));
 
        return new Response(json_encode($model));
 
 
    }
 
    }
 
 
}
 
}
 +
</nowiki>
  
> Configuration
+
== Configuration ==
  
 
Here is the code to put in your app/config/config.yml :
 
Here is the code to put in your app/config/config.yml :
  
 +
<nowiki>
 
sebk_small_orm:
 
sebk_small_orm:
 
 
    connections:
 
    connections:
 
 
        default:
 
        default:
 
 
            type: mysql
 
            type: mysql
 
 
            host: %db_host%
 
            host: %db_host%
 
 
            database: %database_name%
 
            database: %database_name%
 
 
            encoding: utf8
 
            encoding: utf8
 
 
            user:     %db_user%
 
            user:     %db_user%
 
 
            password: %db_password%
 
            password: %db_password%
  
 
    bundles:
 
    bundles:
 
 
        AppAcmeBundle:
 
        AppAcmeBundle:
 
 
            connections:
 
            connections:
 
 
                default:
 
                default:
 
 
                    dao_namespace: App\AcmeBundle\Dao
 
                    dao_namespace: App\AcmeBundle\Dao
 
 
                    model_namespace: App\AcmeBundle\Model
 
                    model_namespace: App\AcmeBundle\Model
 
 
                    validator_namespace: App\AcmeBundle\Validator
 
                    validator_namespace: App\AcmeBundle\Validator
 +
</nowiki>
  
> How to get it
+
== How to get it ==
  
The source code is available on githut : https://github.com/sebk69/SebkSmallOrmBundle
+
The source code is available on githut : [https://github.com/sebk69/SebkSmallOrmBundle https://github.com/sebk69/SebkSmallOrmBundle]
  
 
To use it :
 
To use it :
Line 637: Line 545:
 
Require the bundle with composer:
 
Require the bundle with composer:
  
 +
<nowiki>
 
composer require sebk/small-orm-bundle
 
composer require sebk/small-orm-bundle
 +
</nowiki>
  
 
Register the bundle in app/AppKernel.php:
 
Register the bundle in app/AppKernel.php:
  
 +
<nowiki>
 
public function registerBundles()
 
public function registerBundles()
  
Line 654: Line 565:
  
 
}
 
}
 +
</nowiki>
  
SmallKeyring is a small webapp self hosted to store confidential data like passwords.
+
== Author ==
 
 
1
 
 
 
2
 
 
 
3
 
 
 
4
 
 
 
5
 
 
 
    • Secret passphrase is optionnal
 
 
 
    • It is never stored and used to generate a private key that encrypt all your personnal data
 
 
 
    • Without entering the good password, even the owner of the site can't decrypt your personnal data
 
 
 
This webapp use SmallOrmBundle and symfony 4 to work.
 
  
If you want to test SmallKeyring or develop with it you can generate your localhost environnement in 6 commands lines on this github repo
+
Sébastien Kus<br>
 +
Web developer at La Bécanerie

Latest revision as of 10:08, 26 January 2021

What is SmallOrmBundle ?

SmallOrmBundle is a small ORM for symfony.

The goal of this ORM is to access data with better performances than lasy loading.

Repository

The source code is available on githut : https://github.com/sebk69/SebkSmallOrmBundle

Basic Principles

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.

Creating your first DAO

Here is your first 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")
        ;
    }
}

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 !

$this->addToMany("childs", array("foreignKey" => "childKey"), "ChildModel");

Same as addToOne but with many childs

Creating your first request

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();

     

Automatic generation

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

Database layers

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

Validate and persist model

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));
    }
}

Delete a model

You can use method delete for remove associated record in database :

$model->delete();

Backup model

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->getBackup());

// The two models names are ...
var_dump($user->getName()); // Output : Opheli
var_dump($user2->getName()); // Output : Seb

Transactions

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));
    }
}

Configuration

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

How to get it

The source code is available on githut : https://github.com/sebk69/SebkSmallOrmBundle

To use it :

Require the bundle with composer:

composer require sebk/small-orm-bundle

Register the bundle in app/AppKernel.php:

public function registerBundles()

{

    return array(

        // ...

        new Sebk\SmallOrmBundle\SebkSmallOrmBundle(),

    );

}

Author

Sébastien Kus
Web developer at La Bécanerie