Skip to content

graphis/db

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jasny DB

Build Status Coverage Status

Jasny DB adds OOP design patterns to PHP's database extensions.

Jasny DB is a data access layer (not a DB abstraction layer) for PHP. It does allow you properly structure your model, while still using the methods and functionality of PHP's native database extensions.

Installation

This library is not intended to be installed directly. The Jasny DB library contains design pattern definitions and implementations. It serves as an abstract base for concrete libraries implemented for specific PHP extensions.

Implementations

Connections

Connection objects are use used to interact with a database. Other object must use a connection object do actions like getting getting, saving and deleting data from the DB.

Registry

The static Jasny\DB serves as a factory and registry for database connections. Registered connections can be used globally.

To register a connection use the register($name, $connection) function. To get a registered connection, use the conn($name) function. Connections can be removed from the registry using unregister($name|$connection).

$db = new Jasny\DB\MySQL\Connection();
Jasny\DB::register('foo');

Jasny\DB::conn('foo')->query();

Jasny\DB::unregister('foo');

The same connection may be registered multiple times under different names.

Named connections

Connections implementing the Named interface can register themselves to Jasny\DB using the useAs($name) method. With the getConnectionName() you can get the name of a connection.

$db = new Jasny\DB\MySQL\Connection();
$db->useAs('foo');

Jasny\DB::conn('foo')->query();

If you only have one DB connection name it 'default', since $name defaults to 'default'.

$db = new Jasny\DB\MySQL\Connection();
$db->useAs('default');

Jasny\DB::conn()->query();

Configuration

Instead of using createConnection() and register() directly, you may set Jasny\DB::$config. This static property may hold the configuration for each connection. When using the conn() method, Jasny DB will automatically create a new connection based on the configuration settings.

Jasny\DB::$config = [
    'default' => [
        'driver'    => 'mysql',
        'database'  => 'database',
        'host'      => 'localhost',
        'username'  => 'root',
        'password'  => 'secure',
        'charset'   => 'utf8'
    ],
    'external' => [
        'driver'    => 'rest',
        'host'      => 'api.example.com',
        'username'  => 'user',
        'password'  => 'secure'
    ]
];

Jasny\DB::conn()->query();
Jasny\DB::conn('external')->get("/something");

Jasny\DB::$drivers holds a list of Connection classes with their driver name. The createConnection($settings) method uses the driver setting to select the connection class. The other settings are passed to the connection's constructor.

Entity

An entity is a "thing" you want to represent in a database or other data storages. It can be a new article on your blog, a user in your message board or a permission in your rights management system.

The properties of an entity object is a representation of the data. Entities usually also carry business logic.

Set values

The setValues() methods is a a helper function for setting all the properties from an array and works like a fluent interface.

$foo = new Foo();
$foo->setValues(['red' => 10, 'green' => 20, 'blue' => 30])->doSomething();

Instantiation

Using the new keyword is reserved for creating a new entity.

When the data of an entity is fetched, the __set_state() method is used to create the entity. This method sets the properties of the entity object before calling the constructor.

Active Record

Enities may be implement the Active Record pattern. Active records combine data and database access in a single object.

Fetch

An entity can be loaded from the database using the fetch($id).

$foo = Foo::fetch(10); // id = 10
$foo = Foo::fetch(['reference' => 'myfoo']);

Save

Objects that implement the ActiveRecord interface have a save() method for storing the entity in the database.

$foo->save();
$foo->setValues($data)->save();

Delete

Entities may be removed from the database using the delete() method.

$foo->delete();

Optionally soft deletion can be implemented, so deleted entities can be restored.

$foo->undelete();

Data Mapper

You may choose to separate database logic from business logic by using a Data Mapper. The Data Mapper is responsible for loading entities from and storing them to their database.

You should either use Data Mappers or Active Record, not both. When using Data Mappers, the entities should not be aware of the database and contain no database code (eg SQL queries).

Fetch

An entity can be loaded from the database using the fetch($id).

$foo = FooMapper::fetch(10); // id = 10
$foo = FooMapper::fetch(['reference' => 'myfoo']);

Save

To store entities a Data Mapper implements the save($entity) method.

FooMapper::save($foo);
FooMapper::save($foo->setValues($data));

Delete

Entities may be removed from the database using the delete($entity) method.

FooMapper::delete($foo);

Optionally soft deletion can be implemented, so deleted entities can be restored.

FooMapper::undelete($foo);

Recordset

An entity tends to be a part of a set of data, like a table or collection. If it's possible to load multiple entities from that set, the Active Record or Data Mapper implement the Recordset interface.

The fetch() method returns a single entity. The fetchAll() method returns multiple enities. fetchList() loads a list with the id and description as key/value pairs. The count() method counts the number of entities in the set.

The fetch methods are intended to support only simple cases. For specific cases you SHOULD add a specific method and not overload the basic fetch methods.

Filter

Fetch methods accept a $filter argument. The filter is an associated array with field name and corresponding value. Note that the fetch() methods takes either a unique ID or filter.

A filter SHOULD always return the same or less results that calling the method without a filter.

$foo   = Foo::fetch(['reference' => 'zoo']);
$foos  = Foo::fetchAll(['bar' => 10]);
$list  = Foo::fetchList(['bar' => 10]);
$count = Foo::count(['bar' => 10]);

Optinally filter keys may include an directives. The following directives are supported:

Key Value Description
"field" scalar Field is the value
"field (not)" scalar Field is not the value
"field (min)" scalar Field is equal to or greater than the value
"field (max)" scalar Field is equal to or less than the value
"field" array Field is one of the values in the array
"field (not)" array Field is none of the values in the array

If the field is an array, you may use the following directives

Key Value Description
"field" scalar The value is part of the field
"field (not)" scalar The value is not part of the field
"field (any)" array Any of the values are part of the field
"field (all)" array All of the values are part of the field
"field (none)" array None of the values are part of the field

Filters SHOULD be alligned business logic, wich may not directly align to checking a value of a field. A recordset SHOULD implement a method filterToQuery which converts the filter to a DB dependent query statement. You MAY overload this method to support custom filter keys.

It's save to use $_GET and $_POST parameters directly.

// -> GET /foos?color=red&date(min)=2014-09-01&tags(not)=abandoned&created.user=12345

$result = Foo::fetchAll($_GET);

Metadata

An entity represents an element in the model. The metadata holds information about the structure of the entity. Metadata should be considered static as it describes all the entities of a certain type.

Metadata for a class might contain the table name where data should be stored. Metadata for a property might contain the data type, whether or not it is required and the property description.

Jasny DB support defining metadata through annotations by using Jasny\Meta.

/**
 * Foo entity
 *
 * @supportive yes
 */
class Foo
{
   /**
    * @var string
    * @required
    */
   public $color;
}

Caveat

Metadata can be really powerfull in generalizing and abstracting code. However you can quickly fall into the trap of coding through metadata. This tends to lead to code that's hard to read and maintain.

Only use the metadata to abstract widely use functionality and use overloading to implement special cases.

Type casting

Entities support type casting. This is done based on the metadata. Type casting is implemented by the Jasny\Meta library.

Internal types

For php internal types normal type juggling is used. Values aren't blindly casted. For instance casting "foo" to an integer would trigger a warning and skip the casting.

Objects

Casting a value to a model entity that supports Lazy Loading, creates a ghost object. Entities that implement the Active Record pattern or have a Data Mapper, but do not support Lazy Loading are fetched from the database.

Casting to any other type of object will create a new object normally. For instance casting "bar" to Foo would result in new Foo("bar").

Validation

Entities implementing the Validatable interface, can do some basic validation prior to saving them. This includes checking that all required properties have values, checking the variable type matches and checking if values are uniquely present in the database.

Lazy loading

Jasny DB supports lazy loading of entities by allowing them to be created as ghost. A ghost only hold a limited set of the entity's data, usually only the identifier. When other properties are accessed it will load the rest of the data.

When a value is casted to an entity that supports lazy loading, a ghost of that entity is created.

Soft deletion

Entities that support soft deletion are deleted in such a way that they can restored.

Deleted entities may restored using undelete() or they can be permanently removed using purge().

The isDeleted() method check whether this document has been deleted.

Fetch methods do not return deleted entities. Instead use fetchDeleted($filter) to load a deleted entity. Use fetchAllDeleted($filter) to fetch all deleted entities from the database.

Resultset

Not implemented yet

Maintainable code

To create maintainable code you SHOULD at least uphold the following rules:

  • Don't access the database outside your model classes.
  • Use traits or multiple classes to separate database logic (eg queries) from business.
  • Keep the number of ifs limited. Implement special cases by overloading.

SOLID

SOLID embodies 5 principles principles that, when used together, will make a code base more maintainable over time. While not forcing you to, Jasny DB supports building a SOLID code base.

Methods are kept small and each method is expected to be overloaded by extending the class.

Functionality of Jasny DB is defined in interfaces and defined in traits around a single piece of functionality or design pattern. The use an a specific interface will trigger behaviour. The trait may or may not be used to implement the interface without consequence.

Active Record and SRP

Using the Active Records pattern is considered breaking the Single responsibility principle. Active records tend to combine database logic with business logic a single class.

However this pattern produces code that is more readable and easier to understand. For that reason it remains very popular.

In the end the choice is up to you. Using the Active Record pattern is always optional with Jasny DB. Alternatively you may choose to use Data Mapper for database interaction.

// Active Record
$user = User::fetch(10);
$user->setValues($data);
$user->save();

// Data Mapper
$user = UserMapper::fetch(10);
$user->setValues($data);
UserMapper::save($user);

Code generation

Present in version 1, but not yet available for version 2

API documentation (generated)

http://jasny.github.com/db/docs

About

A DB layer for the masses

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • PHP 100.0%