Harp ORM is a light DataMapper persistence layer for php objects.
// 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 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.
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',
]);
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 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
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.
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]);
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
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
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 |
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');
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
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
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