innobitlab / php-fly-rrm
An entity free 'RRM' to extract structured data from relational DBMS
Requires
- php: >=5.3
- doctrine/dbal: v2.4.2
- monolog/monolog: 1.7.0
- symfony/yaml: v2.4.1
Requires (Dev)
- phpunit/dbunit: >=1.2
- phpunit/phpunit: 3.7.*
This package is not auto-updated.
Last update: 2024-04-27 13:42:38 UTC
README
An entity free "RRM" (Resource Relational Mapping) to extract structured data from relational DBMS
Why a Resource Relational Mapping
Because sometimes you need to extract data from a relational DBMS in a structured way, and maybe you don't want to add an "heavy" ORM with all his dependencies... or you can't beacuse of a legacy context.
Give me an example
Ready, set, go!
Suppose we have this type of database:
users
id | username | password |
---|---|---|
1 | admin | mysecret |
2 | james.white | whitesnow |
contracts
id | name |
---|---|
1 | full-time |
2 | half-time |
banks
id | name |
---|---|
1 | the money free bank |
2 | super bank |
3 | momeny first bank |
employees
id | name | surname | id_contract | id_user_creator | id_user_last_edit |
---|---|---|---|---|---|
1 | Mario | Rossi | 2 | 1 | null |
2 | Giuseppe | Verdi | 1 | 1 | 2 |
payments
id | value | id_employee | id_bank |
---|---|---|---|
1 | 1200.23 | 1 | 2 |
2 | 1540 | 2 | 3 |
3 | 1240.23 | 1 | 2 |
and you have been asked to export this data in a similar structured json format:
{ "employees":[ { "name":"Mario", "surname":"Rossi", "contract":{ "name":"half-time" }, "creator":{ "username":"admin" }, "last-edit":null, "payments":[ { "value":1200.23, "bank":{ "name":"super bank" } }, { "value":1240.23, "bank":{ "name":"super bank" } } ] }, { "name":"Giuseppe", "surname":"Verdi", "contract":{ "name":"full-time" }, "creator":{ "username":"admin" }, "last-edit":{ "username":"james.white" }, "payments":[ { "value":1540, "bank":{ "name":"money first bank" } } ] } ] }
and you have been asked to make this export configurable via a similar YAML file:
resource: alias: 'employees' table: 'employees' primary-key: 'id' fields: - name: 'name' - name: 'surname' relationships: - type: 'many-to-one' join-column: 'id_contract' resource: alias: 'contract' table: 'contracts' primary-key: 'id' fields: - name: 'name' - type: 'many-to-one' join-column: 'id_user_creation' resource: alias: 'creator' table: 'users' primary-key: 'id' fields: - name: 'username' - type: 'many-to-one' join-column: 'id_user_last_edit' resource: alias: 'last-edit' table: 'users' primary-key: 'id' fields: - name: 'username' - type: 'one-to-many' join-column: 'id_employee' resource: alias: 'payments' table: 'payments' primary-key: 'id' fields: - name: 'value' type: 'number' relationships: - type: 'many-to-one' join-column: 'id_bank' primary-key: 'id' fields: - name: 'name'
Installation
Composer
You can install innobitlab/php-fly-rrm
using composer Dependency Manager.
If you need information about installing composer: http://getcomposer.org/doc/00-intro.md#installation-nix
Add this to your composer.json file:
{
"require": {
"innobitlab/php-fly-rrm": "dev-master"
}
}
Usage
Without facade (more flexibility)
<?php use FlyRRM\DataExtraction\DataExtractor; use FlyRRM\Formatting\ArrayFormatter; use FlyRRM\Formatting\Field\FieldFormatterConcreteFactory; use FlyRRM\Hydration\ArrayHydrator; use FlyRRM\Hydration\Field\FieldHydrationConcreteFactory; use FlyRRM\Mapping\Parsing\Yaml\YamlMappingParser; use FlyRRM\QueryBuilding\DBALQueryBuilder; use FlyRRM\QueryExecution\DatabaseConfiguration; use FlyRRM\QueryExecution\DBALQueryExecutor; require_once '..\vendor\autoload.php'; // getting mapping file content $yamlMapping = file_get_contents('emplyees_mapping.yaml'); // instanciate the parser $parser = new YamlMappingParser(); // parsing mapping $resource = $parser->parse($yamlMapping); // instanciate a query builder $dbalQueryBuilder = new DBALQueryBuilder(); // create a database configuration $databaseConf = new DatabaseConfiguration(); $databaseConf->setDatabaseName('supercompany_db'); $databaseConf->setDriver('pdo_mysql'); $databaseConf->setHost('127.0.0.1'); $databaseConf->setPassword('SuperSecret!'); $databaseConf->setPort(3306); $databaseConf->setUsername('myUser'); // instanciate the query executor with the related db config $queryExecutor = new DBALQueryExecutor($databaseConf); // instanciate a data extator with the query builder and the query executor $dataExtractor = new DataExtractor($dbalQueryBuilder, $queryExecutor); // extracting data as plain array $plainData = $dataExtractor->extractData($resource); // instanciate the array hydrator with the appropriate field hydrator factory $dataHydrator = new ArrayHydrator(new FieldHydrationConcreteFactory()); // hydrate into an array all the plain data $hydratedData = $dataHydrator->hydrate($plainData, $resource); // reformat the data (hydrator will hydrate to objects like \DateTime...) $dataFormatter = new ArrayFormatter(new FieldFormatterConcreteFactory()); $formattedData = $dataFormatter->format($hydratedData, $resource); // now you can encode in json $json = json_encode($formattedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
With facade (more easy)
A proper facade to manage all the process end-to-end will be soon available
Mapping
Currently the only implemented mapping parser is a YAML mapping parser. Other parsers can be implemented (ex: json, xml, php arrays).
YAML
The root node must be a resource. A mapping must contain only one root resource.
Resource properties
alias
: mandatory, will be the "name" of the formatted resourcetable
: mandatory, the name of the table where the resource data is storedprimary-key
: mandatory, the name of the primary-key fieldfields
: mandatory, must be a sequence of fieldsrelationships
: optional, must be a sequence of relationship
A resource must have one and only one table.
At the moment only one-field primary keys are supported. Fly-rrm doesn't mind about the primary-key field type, be sure to use a type compatible with the sub-resources primary-key.
A resource must contain one or more fields. A resource could contain one or more relationship.
Field properties
alias
: optional, will be the "name" of the formatted resource fieldname
: mandatory, the name of the field in the resource tabletype
: optional, the type of the fieldformat-string
: optional, the field format string.
If no alias is specified will be used the name.
If no type is specified will be by default a string.
Allowed types are: string
, date
, datetime
, number
.
Currently format-string is supported only on date and datetime types. You can use php's \Datetime::format pattern syntax.
Relationship properties
type
: mandatory, the type of relationshipjoin-column
: mandatory, the name of the join field on dbresource
: mandatory, mapping of the referenced resource
Supported relationship types are: one-to-many
and many-to-one
.
Many to many isn't useful because you always start watching from a resource to another.
You can indent as many resource with relationships as you need.
Connect to database
The only query builder and executor availables are the Docrine DBAL ones. Using DBAL you can perform extraction on many different DBMS.
For futher information about DBAL see: http://www.doctrine-project.org/projects/dbal.html
Hydrating data
As shown in the example you can even use the hydrated array data. This will be a php associative array with alias and proper objects (ex: \DateTime for date and datetime types).
Formatting data
The hydrated array should be formatted before export. This process will transform objects in strings according to the specified format-string. Numbers won't be formatted.
Exporting data
You can encode formatted data to your prefered format (ex. json, xml...)
Methodologies
This project has been developed with the Test Driven Development and Pomodoro technique methodologies. All the production code is covered by tests.
Until this readme the project has required me 55 Pomodoros of development.
Contributing
If you find any bug or you want to share improvements please send a pull-request. Test coverage on bugs or new features will be appreciated.
Credits
Idea and development: Gabriele Tondi info@gabrieletondi.it
Thanks to Innobit s.r.l. for giving me the opportunity to share this code.