Awesome
Hawkbit Database
Object orientated database handling with POPO's, unit of work, identity map and data mapper.
Install
Using Composer
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';
Downloading .zip file
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.
Requirements
The following versions of PHP are supported by this version.
- PHP 5.5
- PHP 5.6
- PHP 7.0
- PHP 7.1
- HHVM
Usage
Connect and go!
- Register a connection
- Register a Mapper if you need to work with Unit of Work and Mappers
- Have fun!
Examples
We also provide Examples for following documentation.
Connections
<?php
use Hawkbit\Database\ConnectionManager;
// setup connection
$connection = ConnectionManager::create([
'url' => 'sqlite:///:memory:',
'memory' => 'true'
]);
Shared Connections
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();
Multiple connections
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');
Prefixing
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.
Migration
Easy schema setup with SQL
<?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 )');
Advanced Migration with phinx
Schema migration is not provided by this Package. We recommend packages like Phinx for advanced migration.
Gateway
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
Create a new entry
<?php
// create a new post
$gateway->create()
->setValue('title', 'Anything')
->setValue('content', 'Lorem Ipsum')
->execute();
Read (select)
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
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
Delete an entry
<?php
$gateway
->delete()
->where('id = 1')
->execute();
Mapper
Data mapper
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
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)
Prepare and load mappers
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();
Modify data
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
Create an entry
<?php
// create entity
$entity->setContent('cnt');
$mapper->create($entity);
Update
Update an entry
<?php
$entity->setContent('FOO');
$mapper->update($entity);
Save
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
Delete an entry
<?php
// delete entity
$mapper->delete($entity);
Fetching data
Find
Find entry by primary key or compound key and return a single entity result or false
<?php
$post = $mapper->find(['id' => 1]);
Custom select
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);
Transactions (Unit of Work)
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.
Build-in doctrine transaction
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;
}
Unit of Work - Transaction with mappers
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();
Change log
Please see CHANGELOG for more information what has changed recently.
Testing
$ composer test
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email mjls@web.de instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.