harp-orm/harp

Light and Flexible ORM without magic

0.3.2 2014-08-28 08:49 UTC

This package is auto-updated.

Last update: 2024-03-21 20:01:54 UTC


README

Harp ORM

Build Status Scrutinizer Quality Score Code Coverage Latest Stable Version

Harp ORM is a light DataMapper persistence layer for php objects.

Quick example

// Model Class
use Harp\Harp\AbstractModel;

class UserModel extends AbstractModel
{
    public static function initialize($config)
    {
        $config
            ->addRel(new Rel\BelongsTo('address', $config, Address::getRepo()));
    }

    public $id;
    public $name;
    public $email;
    public $addressId;

    public function getAddress()
    {
        return $this->get('address');
    }

    public function setAddress(Address $address)
    {
        return $this->set('address', $address);
    }
}

// Saving new model
$user = new UserModel(['name' => 'my name']);
UserRepo::save($user);

// Loading model
$loadedUser = UserRepo::find(1);
var_dump($loadedUser);

// Loading related model
$address = $loadedUser->getAddress();
var_dump($loadedUser->getAddress());

Why?

Why another ORM? At present there are no ORMs that use the latest PHP features. Recent advancements in the php language itself (e.g. traits), and external static analysis tools allow for writing applications that can be easily verified to be correct, however most available ORMs don't use them which makes static code analysis not very useful. And lastly, this package aims to be closer to the metal, smartly using existing PHP classes and extensions to make your models fast, without sacraficing features. Here's the elevator pitch:

  • Uses harp-orm/query and PDO as much as possible greatly increasing performance. It has some very useful features that are not used by current ORMs
  • Full polymorphism support - both "belongs to polymorphic" and "single table inheritance"
  • Proper soft delete, which every part of the code is aware of
  • Lazy loading and eager loading, which works for polymorphic relations
  • Save multiple models with grouped queries for increased performance
  • No enforcement of folder structure, place your classes wherever you want
  • Uses PSR coding style and symfony naming conventions for more clean and readable codebase
  • Save all associated models with a single command, with query grouping under the hood.
  • Fully extensible interface. Uses native PHP5 constructs to allow extending with traits and interfaces
  • All methods have proper docblocks so that static code analyses of code built on top of this is more accurate.

Instalation

Harp uses composer so intalling it is as easy as:

composer require harp-orm/harp:~0.3.0

It uses harp-orm/query for connecting to the database, so you'll also need to configure the connection:

use Harp\Query\DB;

DB::setConfig([
    'dsn' => 'mysql:dbname=harp-orm/harp;host=127.0.0.1',
    'username' => 'root',
    'password' => 'root',
]);

Subsections

Defining Models

Here's an example model class.

use Harp\Harp\AbstractModel;

class User extends AbstractModel
{
    // Configure the "Repo" object for this model class
    // This holds all the database-specific configs,
    // as well as relations with other models
    public static function initialize($config)
    {
        $config

            // Configure relations
            ->addRel(new Rel\BelongsTo('address', $config, AddressRepo::get()));

            // Configure validations
            ->addAssert(new Assert\Present('name'))
            ->addAssert(new Assert\Email('name'));
    }

    // Public properties persisted as columns in the table
    public $id;
    public $name;
    public $email;
    public $addressId;
}

All the public properties get persisted in the database, using the native types if available.

Tip Once related objects have been loaded, they will be cached and returned on further requests, however the data is not kept in the model itself, thus if you do a var_dump on an model it will return only data of the model itself and will keep your stack traces readable.

Detailed list of all the configuration methods:

Configuration Method Description
setTable($table) Set the name of the database table, defaults to the short class name of the model
setDb($dbName) Set alternative database connection, this will use alternative database configurations you've setup
setSoftDelete($isSoftDelete) Set to true if you want this model to be soft deleted. This is configured automatically by the SoftDeleteTrait. More on soft delete later
setInherited($isInherited) Set to true if this repo will be inherited by other repo using Single table inheritance. This is configured automatically by the InheritedTrait.
setPrimaryKey($primaryKey) Sets the property/column to be used for primary key, "id" by default
setNameKey($nameKey) Sets the property/column to be used for name key - will be used for findByName method on the repo. Defaults to "name"
addRel(AbstractRel $rel) Add a link to a related model. Read about Relations
addRels(array $rels) Add multiple rel objects.
addAssert(AbstractAssert $assert) Add an assertion for this model. Read about Assertions
addAsserts(array $asserts) Add multiple asserts
addSerializer(AbstractSerializer) Add a property serializer. Read about Serializers
addSerializers(array $serializers) Add multiple serializer objects
addEventBefore($event, $callback) Add event listener, to be triggered before a specific event
addEventAfter($event, $callback) Add event listener to be triggered after a specific event

For more about events, read Extending using events

Retrieving from the database

Retrieving models from the database (as well as saving but on that later) are handled with static methods on the model class. To find models by their primary key use the find method.

$user1 = User::find(8);
$user2 = User::find(23);

If the model has a "name" property (or a nameKey configured) you can use findByName method.

$user1 = User::findByName('Tom');
$user2 = User::findByName('John');

For more complex retrieving you can use the findAll method, which returns a 'Find' object. It has a rich interface of methods for constructing an sql query:

$select = User::findAll();
$select
    ->where('name', 'John')
    ->whereIn('type', [1, 4])
    ->joinRels(['address' => 'city'])
    ->limit(10);

$users = $select->load();

foreach ($users as $user) {
    var_dump($user);
}

All the models retrieved from the database are stored in an "identity map". So that if at a later time, the same model is loaded again. It will return the same php object, associated with the db row.

$user1 = User::find(10);
$user2 = User::find(10);

// Will return true
echo $user1 === $user2;

Detailed docs for findAll

Persisting Models

When models have been created, modified or deleted they usually need to be persisted again. This is done with the "save" method on the model.

$user = User::find(10);
$user->name = 'new name';

$address = new Address(['location' => 'home']);
$user->setAddress($address);

// This will save the user, the address and the link between them.
User::save($user);

$user2 = User::find(20);
$user2->delete();

// This will remove the deleted user from the database.
User::save($user2);

When you add / remove or otherwise modify related models they will be saved alongside your main model.

Preserving multiple models

You can presever multiple models of the same repo at once (with query grouping) using the saveArray method. Just pass an array of models.

$save = User::saveArray([$user1, $user2, $user3]);

Soft deletion

If you need to keep the models in the database even after they are deleted - e.g. logical deltion, you can use the SoftDeleteTrait

// Model File
use Harp\Harp\AbstractModel;
use Harp\Harp\Model\SoftDeleteTrait;

class Order extends AbstractModel
{
    use SoftDeleteTrait;

    public static function initialize($config)
    {
        // The trait has its own initialize method that you'll have to call
        SoftDeleteTrait::initialize($config);
    }
}

$order = Order::find(2);

$order->delete();

// This will issue an UPDATE instaead of a DELETE, marking this row as "deleted".
Order::save($order);

$order = Order::find(2);

// This will return true
echo $order->isVoid();

This adds a some of methods to your model. Read about soft deletion in detail here

Inherited

Sometimes you need several models to share the same database table - e.g. if there is just a slight variation of the same functionality. This is called Single Table Inheritance.

Harp ORM supports inheriting models out of the box. Read about inexperience in detail here

Model states

Throughout their lives Models have different states (Harp\Harp\Model\State). They can be:

State Description
State::PENDING Still not persisted in the database. This is the default state of models. Will be inserted in the database when persisted
State::DELETED Marked for deletion. When persisted will execute a DELETE query
State::SAVED Retrieved from the database. When is changed
State::VOID This represents a "non-existing" model, e.g. when you try to retrieve a model that does not exists

There are several methods for working with model states:

Method Description
setState($state) Set the state on the model
getState() Retrieve the current state
isSaved() Return true if state is State::SAVED
isPending() Return true if state is State::PENDING
isDeleted() Return true if state is State::DELETED
isVoid() Return true if state is State::VOID

Dirty Tracking

Models track all the changes to their public properties to minimise updates to the database. You can use that functionality yourself by calling these methods:

Method Description
getOriginals() Get an array with all the original values of the properties
getOriginal($name) Get a specific original value, returns null if value does not exist
getChange($name) Returns an array with [original, changed], or null if there is no change
getChanges() Return an array with [name => new value] for all the changes
hasChange($name) Return true if the property has been changed
isEmptyChanges() Return true if there are no changes
isChanged() Return true if there are any changes
resetOriginals() Set the current property values as the "original". This is called when a model has been saved
getProperties() Get an array with the current values of all the public properties
setProperties(array $properties) Set public properties with a [property name => value] array

Example:

$user = User::find(10);

// returns false
echo $user->isChanged();

$user->name = 'new test';

// returns true
$user->isChanged();

// returns true
$user->hasChange('name');

// returns ['name' => 'new test']
$user->getChanges();

// returns original name
$user->getOriginal('name');

$user->resetOriginal();

// returns 'new test'
$user->getOriginal('name');

Extending

When you want to write packages that extend functionality of Harp ORM, or simply share code between your models, you can use PHP's native Traits. They allow you to statically extends classes. All of the internals of Harp ORM are built around allowing you to accomplish this easily as this is the preferred way of writing "behaviours" or "templates".

Apart from that you will be able to add event listeners for various events in the life-cycle of models.

Read about extending in detail here

Direct database access.

There are times when you'll need to get to the bare metal and write custom sqls. To help you do that you can use the internal Query classes directly.

$update = User::insertAll()
    ->columns(['name', 'id'])
    ->select(
        Profile::selectAll()
            ->clearColumns()
            ->column('name')
            ->column('id')
            ->where('name', 'LIKE', '%test')
    );

// INSERT INTO User (name, id) SELECT name, id FROM Profile WHERE name LIKE '%test'
$update->execute();

More details about custom queries you can read the Query section

License

Copyright (c) 2014, Clippings Ltd. Developed by Ivan Kerin as part of clippings.com

Under BSD-3-Clause license, read LICENSE file.

Icon made by Freepik from www.flaticon.com