chrisandchris/passive-record-orm

A database layer, passive record and orm abstraction layer for your php application.


Keywords
database, orm, symfony, mysql, query, pgsql, postgres, builder, querybuilder, rowmapper, database-abstraction, doctrine-orm, postgresql, query-builder, query-language, symfony2
License
GPL-2.0

Documentation

A QueryBuilder and passive record ORM for Symfony2

Build Status Code Climate Test Coverage Version Downloads Licence

Despite it's package name, it's not simply a row mapper. And it's not simply for Symfony. This project is a QueryBuilder and a Mapper for SQL Result Sets, both combined but still very separated, so you can use them independent.

<?php

use ChrisAndChris\Common\RowMapperBundle\Services\Model\ConcreteModel;

class DemoRepo {
    /** @var ConcreteModel  */
    private $model;
    
    public function __construct(ConcreteModel $model){
        $this->model = $model;
    }
    
    public function getCustomerName($customerId) {
        $query = $this->model->getDependencyProvider()->getBuilder()->select()
            ->field('customer_name')
            ->table('customer')
            ->where()
                ->field('customer_id')->equals()->value($customerId)
            ->close()
            ->getSqlQuery();

        return $this->model->runWithFirstKeyFirstValue($query);
    }
}

This doc gives a short overview of all the possibilities this package provides. We are moving the contents continuously to the doc/ directory, so look more detailed information up there.

Was it does

  • Opens and handles MySQL-Connection
  • Provides a simple interface for building prepared statements and querying the database
  • Provides a simple interface for mapping the results to classes

Internals

The basic internal principal is the following:

  • There are Types (simple key-value classes) which represent a part of a statement
  • The query gets parsed using a Parser and the same-named snippets (they contain sql)
  • The query is returned

How To Use

## Configuration Configure your symfony2 project as you do always. The bundle uses the database information stored in the parameters.yml and automatically connects to the given database.

Actually, there is no further configuration possible.

A simple query

Let's create a service definition:

services:
    project.demo_repo:
        class: DemoRepo
        arguments: ['@common_rowmapper.model']

Create the repository:

<?php

use ChrisAndChris\Common\RowMapperBundle\Services\Model\ConcreteModel;

class DemoRepo {
    /** @var ConcreteModel  */
    private $model;
    
    public function __construct(ConcreteModel $model){
        $this->model = $model;
    }
    
    public function getCustomerName($customerId) {
        $query = $this->model->getDependencyProvider()->getBuilder()->select()
            ->field('customer_name')
            ->table('customer')
            ->where()
                ->field('customer_id')->equals()->value($customerId)
            ->close()
            ->getSqlQuery();

        return $this->model->runWithFirstKeyFirstValue($query);
    }
}

If you want to map a more complicated query to a class, use something like this:

<?php

use ChrisAndChris\Common\RowMapperBundle\Entity\Entity;

class CustomerEntity implements Entity {
    public $customerId;
    public $name;
    public $street;
    public $zip;
    public $city;
}

And to map, use this method

<?php

use ChrisAndChris\Common\RowMapperBundle\Services\Model\ConcreteModel;

class DemoModel {
    
    /** @var ConcreteModel  */
    private $model;
    
    public function __construct(ConcreteModel $model){
        $this->model = $model;
    }
    
    public function getCustomer($customerId) {
        $query = $this->model->getDependencyProvider()->getBuilder()->select()
            ->fieldlist([
                'customer_id' => 'customerId',
                'cus_name' => 'name',
                'street',
                'zip',
                'city'
            ])
            ->table('customer')
            ->where()
                ->field('customer_id')->equals()->value($customerId)
            ->close()
            ->getSqlQuery();

        return $this->model->run($query, new SomeEntity());
    }
}

Some more information

The field() method

You could use an array for separating database, table, field:

field(['database', 'table', 'field'])`

If you fetch single fields, you must append a comma by yourself:

->field('field1')->c()
->field('field2')->c()

You could also give a closure as parameter:

->field(function () { return $value; });

The value() method

Use this method to append a parameter to the query:

->value($someValue);
->value(function () { return $someValue; });

The fieldlist() method

This method is even much more powerful, use it as follows:

Simple key-value usage:

fieldlist([
    'field' => 'alias',
    'customer_id' => 'customerId',
    'cus_name' => 'name'
])

Specify database, table, field:

fieldlist([
    'database:table:field' => 'alias'
]);

Mix anything

fieldlist([
    'database:table:field' => 'alias',
    'field1', // fetched by real field name
    'field2' => 'alias1'
]);

The f(), where(), order(), groupBy()

Any of these four types open so-called "braces". A brace represents a kind of sub-query which is fully independent from the query before. In its internals, during parsing this sub-query, the parser has principally no access to the other statements.

So, if you finish one of these, simply call close() or end() to close the brace:

->where()
    ->field('field1')->equals()->value(true)
->close()

The raw()

Because of the lack of time and to fulfill any requirement, I simply implemented a raw method. And gladly, this method is able to use parameters :D

->raw('SELECT customer_id FROM customer WHERE customer_name LIKE ?', [
    '%github%'
]);

The in()

You can simply build IN-clauses with the two following methods:

// option a
->in([1, 2, 3, 4, 5, 6])
// option b
->in()
    ->select()
    ->value(1)
->close()

Option A uses prepared statements all-the-way, any value within the array gets is way as a parameter to the database.

Conditional appending

There are three methods to provide conditional appending:

  • _if()
  • _else()
  • _end()

You are allowed to nest ifs, and you are allowed to push a closure as parameter to the if:

->_if($condition === true)
    ->where()
    ->_if(function() { return $condition === true; })
        // ...
    ->_end()
        // ...
    ->close()
->_else()
    //
->_end()

Some other methods

  • f() - for functions
  • where() - build wheres
  • any() - a god-blessed star (evil SELECT *)
  • value() - a parameter
  • null() - a sql NULL
  • isNull() - compares to null using IS NULL
  • join() - join tables
  • using() - using clause for joined tables
  • on() - on clause for joined tables
  • union() - create UNION statements
  • asLong() - creating while loop
  • each() - creating each loop