Object orientated database handling with POPO's, unit of work, identity map and data mapper.
Hawkbit Database is available on Packagist and can be installed using Composer. This can be done by running the following command or by updating your composer.json
file.
composer require hawkbit/database
composer.json
{
"require": {
"hawkbit/database": "~1.0"
}
}
Be sure to also include your Composer autoload file in your project:
<?php
require __DIR__ . '/vendor/autoload.php';
This project is also available for download as a .zip
file on GitHub. Visit the releases page, select the version you want, and click the "Source code (zip)" download button.
The following versions of PHP are supported by this version.
- PHP 5.5
- PHP 5.6
- PHP 7.0
- PHP 7.1
- HHVM
- Register a connection
- Register a Mapper if you need to work with Unit of Work and Mappers
- Have fun!
We also provide Examples for following documentation.
<?php
use Hawkbit\Database\ConnectionManager;
// setup connection
$connection = ConnectionManager::create([
'url' => 'sqlite:///:memory:',
'memory' => 'true'
]);
In huge applications, you need to be able to access connections at many points. Simply share your connection
<?php
use Hawkbit\Database\ConnectionManager;
ConnectionManager::getInstance()->add($connection);
You may want to save time or lines of code and add connection directly
<?php
use Hawkbit\Database\ConnectionManager;
ConnectionManager::getInstance()->add([
'url' => 'sqlite:///:memory:',
'memory' => 'true'
]);
and access connection at another point of your application
<?php
use Hawkbit\Database\ConnectionManager;
ConnectionManager::getInstance()->get();
In some cases you need two ore more connections. Therefore connections could have a name, the default name is default
(Hawkbit\Database\ConnectionManager::DEFAULT_CONNECTION
).
<?php
use Hawkbit\Database\ConnectionManager;
// add connections
ConnectionManager::getInstance()->add([
'url' => 'sqlite:///:memory:',
'memory' => 'true'
]);
ConnectionManager::getInstance()->add([
'url' => 'mysql://<user>:<password>@<host>/<database>?charset=utf8',
], 'second');
// and access connections
$default = ConnectionManager::getInstance()->get();
$second = ConnectionManager::getInstance()->get('second');
On a shared database it is use full to to prefix tables with a specific name, e.g. application abbreviation. You need to set the prefix on your connection. The system is prefixing all table names automatically.
Add PHP 7.1 support
<?php
// setup prefixes
$connection->setPrefix('custom_');
$gateway = $connection->createGateway('user'); // connects to custom_user table
As you can see you (as do mapper) pass the tablename and internally the tablename will be prefixed.
<?php
use Hawkbit\Database\ConnectionManager;
// add connections
$connection = ConnectionManager::getInstance()->get();
// setup schema
$connection->exec('CREATE TABLE post (id int, title VARCHAR(255), content TEXT, date DATETIME DEFAULT CURRENT_DATE )');
Schema migration is not provided by this Package. We recommend packages like Phinx for advanced migration.
The gateway mediates between database and mapper. It is able to execute table or view specific operations and provides CRUD queries.
Create a new gateway from connection
<?php
$gateway = $connection->createGateway('post');
Create a new entry
<?php
// create a new post
$gateway->create()
->setValue('title', 'Anything')
->setValue('content', 'Lorem Ipsum')
->execute();
Fetch entries as array (refer to \PDO::FETCH_ASSOC
). Entries do have following array representation
<?php
$posts = [
0 => [
'id' => 1,
'title' => 'Anything',
'content' => 'Lorem Ipsum',
],
1 => [
'id' => 2,
'title' => 'Anything else',
'content' => 'More text',
]
];
<?php
use Doctrine\DBAL\Types\Type;
$posts = $gateway
->select()
->where('title = ?')
->setParameter(0, 'Anything', Type::STRING)
->execute()
->fetchAll();
// process entries
foreach ($posts as $post){
echo sprintf('<h1>%s</h1><p>%s</p>', $post['title'], $post['content']);
}
You could also use doctrines expression builder combined with QueryBuilder::createPositionalParameter
and
QueryBuilder::createNamedParameter
for more complex queries!
Update an entry
<?php
$gateway
->update()
->set('content', 'Cool text instead of Lorem Ipsum, but Lorem Ipsum is cool at all!')
->where('id = 1')
->execute();
Delete an entry
<?php
$gateway
->delete()
->where('id = 1')
->execute();
The data mapper is mapping data to entities and extracting data from entities. The mapper is also aware of database table configuration and converts data types into both directions - PHP and database. It utilizes an Identity Map which ensures that an object is only load once.
It is recommended to extend the mapper with custom logic for accessing associated data or other specific data sets. Data association / Data relation are not supported at this point!
The combination of mapper and entities may have the flavor of Table-Data-Gateway with passive records.
Entities represent a single entry (row) of a database table or view. It is a set of Setters and Getters, without any logic - passive record. Entities are Plain Old PHP Objects (POPO)
Register mapper to mapper locator to preload configuration and ensure access. An Exception is thrown when locator is unable to find a mapper.
<?php
use Application\Persistence\Mappers\PostMapper;
// register mappers
$connection->getMapperLocator()->register(PostMapper::class);
Locate a registered mapper
<?php
use Application\Persistence\Entities\Post;
$entity = new Post();
$mapper = $connection->loadMapper($entity);
Locate a registered mapper by entity
<?php
use Application\Persistence\Entities\Post;
// entity instance is also allowed
$mapper = $connection->loadMapper(Post::class);
// or create entity from mapper
Create entity from mapper, if you don't want to create a new instance of entity
<?php
$entity = $mapper->createEntity();
The mapper is able determine entity state and save (create or update) data. The mapper is modifying the same entity instance. All changes which have been made in database are also stored into the entity at the same time.
Create an entry
<?php
// create entity
$entity->setContent('cnt');
$mapper->create($entity);
Update an entry
<?php
$entity->setContent('FOO');
$mapper->update($entity);
The mapper is also able to detect a new or existing entity and choose to create or update an related entry in database.
<?php
$entity->setContent('FOO');
$entity->setTitle('Philosophy of bar, baz & foo');
// create or update
$mapper->save($entity);
Delete an entry
<?php
// delete entity
$mapper->delete($entity);
Find entry by primary key or compound key and return a single entity result or false
<?php
$post = $mapper->find(['id' => 1]);
Fetch entries as a list of entities. Entries do have following representation
<?php
$posts = [
0 => new Post(), // 'id': 1, 'title': 'Anything', 'content': 'Lorem Ipsum'
1 => new Post(), // 'id': 2, 'title': 'Anything else', 'content': 'More text'
];
A select is taking all valid kinds of callable to modify the query.
<?php
use \Doctrine\DBAL\Query\QueryBuilder;
$posts = $mapper->select(function(QueryBuilder $queryBuilder){
// build even more complex queries
$queryBuilder->where('id = 1');
});
// process entries
/** @var \Application\Persistence\Entities\Post $post */
foreach ($posts as $post){
echo sprintf('<h1>%s</h1><p>%s</p>', $post->getTitle(), $post->getContent());
}
Fetch only one entry will return an entity instead of an entity list
<?php
use \Doctrine\DBAL\Query\QueryBuilder;
$one = true;
$post = $mapper->select(function(QueryBuilder $queryBuilder){
// build even more complex queries
$queryBuilder->where('id = 1');
}, ['*'], $one);
For data consistency you want to use transactions. Basically all queries are wrapped with a transaction and automatically committed if auto-commit option is enabled. You could also create, update and delete a huge set of data and commit them a once.
To use a unit of work, you need to register a mapper. In some cases, like prototyping you want use gateway for quick access. Therefore you should use build-in transaction.
<?php
try {
$connection->beginTransaction();
$gateway->update()->set('content', 'blah')->where('id = 1');
$gateway->update()->set('content', 'blub')->where('id = 2');
$gateway->delete()->where('id in(32,45,16)');
$gateway->create()->setValue('content', 'i am new!')->execute();
$connection->commit();
} catch (\Exception $e) {
$connection->rollBack();
// you may want to pass exception to next catch
throw $e;
}
Transactions need registered mappers to work correctly.
Modify you data in any order, unit of work is sorting the execution of create, update and delete for you. If all
modifications are done, you need to execute UnitOfWork::commit
Please refer to the following example of unit of work:
<?php
use Application\Persistence\Entities\Post;
$unitOfWork = $connection->createUnitOfWork();
$entity = new Post();
// or create entity from mapper
//$entity = $mapper->createEntity();
// create entity
$entity->setContent('cnt');
$unitOfWork->create($entity);
// commit transaction
if(false === $unitOfWork->commit()){
//handle exception
$unitOfWork->getException();
// get last processed entity
$unitOfWork->getLastProcessed();
}
// get all modified entities
$unitOfWork->getModified();
// get a list of all entities by state
$unitOfWork->getProcessed();
// find entity by primary key or compound key
$mapper = $connection->loadMapper($entity);
$mapper->find(['id' => 1]);
// update entity
$entity->setContent('FOO');
$unitOfWork->update($entity);
// delete entity
$unitOfWork->delete($entity);
// commit transaction, again
$unitOfWork->commit();
Please see CHANGELOG for more information what has changed recently.
$ composer test
Please see CONTRIBUTING for details.
If you discover any security related issues, please email mjls@web.de instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.