├── .editorconfig
├── src
├── MapperAwareInterface.php
├── Relations
│ ├── RelationsAwareInterface.php
│ ├── RelationInterface.php
│ ├── HasOne.php
│ ├── RelationTrait.php
│ ├── BelongsTo.php
│ ├── HasMany.php
│ └── ManyToMany.php
├── Query
│ ├── QueryAwareInterface.php
│ └── Events
│ │ ├── AbstractQueryEvent.php
│ │ ├── QueryBuilderEvent.php
│ │ └── QueryResultEvent.php
├── MapperFactoryInterface.php
├── QueryFactoryInterface.php
├── Entity
│ ├── ProviderFactoryInterface.php
│ ├── ProviderFactoryTrait.php
│ ├── EntityAwareInterface.php
│ ├── ProviderInterface.php
│ ├── TransformerInterface.php
│ ├── EntityAwareTrait.php
│ ├── DefinitionInterface.php
│ ├── Provider.php
│ ├── Transformer.php
│ └── Definition.php
├── QueryInterface.php
├── MapperFactoryTrait.php
├── ConnectionAwareInterface.php
├── CacheAwareTrait.php
├── RepositoryInterface.php
├── Hydrator
│ ├── HydratorInterface.php
│ └── EntityHydrator.php
├── ConnectionAwareTrait.php
├── ConnectionManagerInterface.php
├── EventEmitterFactoryInterface.php
├── GatewayInterface.php
├── MapperInterface.php
├── AbstractRepository.php
├── Support.php
├── EventEmitterFactoryTrait.php
├── Gateway.php
├── Connection.php
├── ConnectionManager.php
├── Mapper.php
└── Query.php
├── phpunit.xml
├── LICENSE.md
├── composer.json
├── CONTRIBUTING.md
├── ChangeLog-1.0.md
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # For more information about the properties used in
2 | # this file, please see the EditorConfig documentation:
3 | # http://editorconfig.org/
4 |
5 | root = true
6 |
7 | [*]
8 | charset = utf-8
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 | indent_size = 2
13 | indent_style = space
14 |
15 |
16 | [*.{php,js,html,css,xml,phtml,md,markdown}]
17 | indent_size = 4
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
22 | [*.bat]
23 | end_of_line = crlf
24 |
--------------------------------------------------------------------------------
/src/MapperAwareInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 01.03.2016
10 | * Time: 10:42
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | interface MapperAwareInterface
18 | {
19 | /**
20 | * Get entity mapper
21 | *
22 | * @return MapperInterface
23 | */
24 | public function getMapper();
25 | }
26 |
--------------------------------------------------------------------------------
/src/Relations/RelationsAwareInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 01.03.2016
10 | * Time: 09:38
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 |
17 | use Blast\Orm\Query;
18 |
19 | interface RelationsAwareInterface
20 | {
21 | /**
22 | * @return RelationInterface[]
23 | */
24 | public function getRelations();
25 | }
26 |
--------------------------------------------------------------------------------
/src/Query/QueryAwareInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 01.03.2016
10 | * Time: 10:43
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Query;
15 |
16 |
17 | use Blast\Orm\Query;
18 |
19 | interface QueryAwareInterface
20 | {
21 | /**
22 | * Get modified query
23 | *
24 | * @return Query
25 | */
26 | public function getQuery();
27 | }
28 |
--------------------------------------------------------------------------------
/src/MapperFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 16.03.2016
10 | * Time: 17:25
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | interface MapperFactoryInterface
18 | {
19 | /**
20 | * Create a new Mapper for given entity.
21 | *
22 | * @param $entity
23 | * @return Mapper
24 | */
25 | public function createMapper($entity);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/QueryFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 13.04.2016
10 | * Time: 08:34
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | interface QueryFactoryInterface
18 | {
19 | /**
20 | * Create a new query for given entity.
21 | *
22 | * @param $entity
23 | *
24 | * @return Mapper
25 | */
26 | public function createQuery($entity = null);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Entity/ProviderFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 16.03.2016
10 | * Time: 17:23
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 |
17 | interface ProviderFactoryInterface
18 | {
19 | /**
20 | * Create a new provider for given entity
21 | *
22 | * @param $entity
23 | * @return Provider
24 | */
25 | public function createProvider($entity);
26 | }
27 |
--------------------------------------------------------------------------------
/src/Entity/ProviderFactoryTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 16.03.2016
10 | * Time: 17:22
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 |
17 | trait ProviderFactoryTrait
18 | {
19 |
20 | /**
21 | * Create a new provider for given entity
22 | *
23 | * @param $entity
24 | * @return Provider
25 | */
26 | public function createProvider($entity){
27 | return new Provider($entity);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/Entity/EntityAwareInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 23.02.2016
10 | * Time: 10:20
11 | *
12 | */
13 | namespace Blast\Orm\Entity;
14 |
15 | interface EntityAwareInterface
16 | {
17 | /**
18 | * @return array|\stdClass|\ArrayObject|object
19 | */
20 | public function getEntity();
21 |
22 | /**
23 | * @param array|\ArrayObject|\stdClass|object $entity
24 | * @return $this
25 | */
26 | public function setEntity($entity);
27 | }
28 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 | ./tests/Stubs
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/QueryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 23.02.2016
10 | * Time: 10:19
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 | use Blast\Orm\Hydrator\HydratorInterface;
17 |
18 | interface QueryInterface
19 | {
20 | /**
21 | * Fetch data for entity
22 | *
23 | * @param string $option
24 | * @return array|\SplStack|\ArrayObject|object
25 | * @throws \Doctrine\DBAL\DBALException
26 | */
27 | public function execute($option = HydratorInterface::HYDRATE_AUTO);
28 | }
29 |
--------------------------------------------------------------------------------
/src/MapperFactoryTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 16.03.2016
10 | * Time: 17:24
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | trait MapperFactoryTrait
18 | {
19 | /**
20 | * Create a new Mapper for given entity or provider and
21 | * pass additional database connection.
22 | *
23 | * @param $entity
24 | * @param null $connection
25 | * @return Mapper
26 | */
27 | public function createMapper($entity, $connection = null){
28 | return new Mapper($entity, $connection);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/ConnectionAwareInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 15.03.2016
10 | * Time: 20:33
11 | */
12 |
13 | namespace Blast\Orm;
14 |
15 |
16 | interface ConnectionAwareInterface
17 | {
18 | /**
19 | * Get current connection
20 | *
21 | * @return \Doctrine\DBAL\Driver\Connection|\Doctrine\DBAL\Connection
22 | */
23 | public function getConnection();
24 |
25 | /**
26 | * @param \Doctrine\DBAL\Driver\Connection|null $connection
27 | *
28 | * @return $this
29 | */
30 | public function setConnection($connection);
31 | }
32 |
--------------------------------------------------------------------------------
/src/Entity/ProviderInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 07.03.2016
10 | * Time: 12:03
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 |
17 | use Blast\Orm\MapperAwareInterface;
18 | use Blast\Orm\Relations\RelationsAwareInterface;
19 | use Doctrine\DBAL\Schema\Column;
20 | use Doctrine\DBAL\Schema\Index;
21 |
22 | interface ProviderInterface extends EntityAwareInterface
23 | {
24 |
25 | const DEFAULT_PRIMARY_KEY_NAME = 'id';
26 |
27 | /**
28 | * @return DefinitionInterface
29 | */
30 | public function getDefinition();
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/CacheAwareTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 27.07.2016
10 | * Time: 10:22
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | trait CacheAwareTrait
18 | {
19 |
20 | /**
21 | * @return \Doctrine\Common\Cache\Cache
22 | */
23 | public function getMetaDataCache(){
24 | return ConnectionManager::getInstance()->get()->getMetaDataCache();
25 | }
26 |
27 | /**
28 | * @return \Doctrine\Common\Cache\Cache
29 | */
30 | public function getReflectionCache(){
31 | return ConnectionManager::getInstance()->get()->getReflectionCache();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Relations/RelationInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 12:07
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 | use Blast\Orm\Entity\EntityAwareInterface;
17 | use Blast\Orm\Query;
18 |
19 | interface RelationInterface extends EntityAwareInterface
20 | {
21 | /**
22 | * @return Query
23 | */
24 | public function getQuery();
25 |
26 | /**
27 | * Name of relation
28 | *
29 | * @return string
30 | */
31 | public function getName();
32 |
33 | /**
34 | * @return array|\ArrayObject||bool
35 | */
36 | public function execute();
37 | }
38 |
--------------------------------------------------------------------------------
/src/Relations/HasOne.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 16:37
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 |
17 | use Blast\Orm\Hydrator\HydratorInterface;
18 |
19 | class HasOne extends HasMany
20 | {
21 | /**
22 | * @return \ArrayObject|object
23 | */
24 | public function execute()
25 | {
26 | return $this->getQuery()->execute(HydratorInterface::HYDRATE_ENTITY);
27 | }
28 |
29 | /**
30 | * Get relation query
31 | *
32 | * @return \Blast\Orm\Query
33 | */
34 | public function getQuery()
35 | {
36 | return parent::getQuery()->setMaxResults(1);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/Entity/TransformerInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 08:02
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 | /**
17 | * Transform configuration into entity and entity definition.
18 | *
19 | * @package Blast\Orm\Entity
20 | */
21 | interface TransformerInterface
22 | {
23 |
24 | /**
25 | * Transform configuration into entity and entity definition. Configuration could be a
26 | * string (class name or table name), array (convert to a definition),
27 | * a Definition instance or an Entity instance.
28 | *
29 | * @param $configuration
30 | * @return mixed
31 | */
32 | public function transform($configuration);
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/Query/Events/AbstractQueryEvent.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 25.02.2016
10 | * Time: 13:21
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Query\Events;
15 |
16 |
17 | use League\Event\AbstractEvent;
18 |
19 | abstract class AbstractQueryEvent extends AbstractEvent
20 | {
21 |
22 | private $canceled = false;
23 |
24 | /**
25 | * Check if execution of query is canceled
26 | * @return boolean
27 | */
28 | public function isCanceled()
29 | {
30 | return $this->canceled;
31 | }
32 |
33 | /**
34 | * @param boolean $canceled
35 | */
36 | public function setCanceled($canceled)
37 | {
38 | $this->canceled = $canceled;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/Entity/EntityAwareTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 23.02.2016
10 | * Time: 10:20
11 | *
12 | */
13 | namespace Blast\Orm\Entity;
14 |
15 | trait EntityAwareTrait
16 | {
17 | /**
18 | * @var array|\stdClass|\ArrayObject|object
19 | */
20 | private $entity;
21 |
22 | /**
23 | * @return array|\stdClass|\ArrayObject|object
24 | */
25 | public function getEntity()
26 | {
27 | return $this->entity;
28 | }
29 |
30 | /**
31 | * @param array|\ArrayObject|\stdClass|object|string $entity
32 | * @return $this
33 | */
34 | public function setEntity($entity)
35 | {
36 | $this->entity = $entity;
37 | return $this;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/RepositoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 10:20
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | interface RepositoryInterface
18 | {
19 | /**
20 | * Find entity by primary key
21 | *
22 | * @param mixed $primaryKey
23 | * @return \ArrayObject|\stdClass|object|array
24 | */
25 | public function find($primaryKey);
26 |
27 | /**
28 | * Get a collection of all entities
29 | *
30 | * @return \SplStack|array
31 | */
32 | public function all();
33 |
34 | /**
35 | * Save new or existing entity
36 | *
37 | * @param object|array $data
38 | * @return int
39 | */
40 | public function save($data);
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Marco Bunge
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/Hydrator/HydratorInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 01.03.2016
10 | * Time: 15:46
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Hydrator;
15 |
16 |
17 | use Zend\Hydrator\ExtractionInterface;
18 |
19 | interface HydratorInterface
20 | {
21 | /**
22 | *
23 | */
24 | const HYDRATE_COLLECTION = 'collection';
25 | /**
26 | *
27 | */
28 | const HYDRATE_ENTITY = 'entity';
29 |
30 | /**
31 | *
32 | */
33 | const HYDRATE_RAW = 'raw';
34 |
35 | /**
36 | *
37 | */
38 | const HYDRATE_AUTO = 'auto';
39 |
40 | /**
41 | * Hydrate values to given object
42 | *
43 | * @param array $data
44 | * @param string $option
45 | * @return mixed
46 | */
47 | public function hydrate($data = [], $option = self::HYDRATE_AUTO);
48 |
49 | /**
50 | * Extract values from given object
51 | *
52 | * @return array
53 | */
54 | public function extract();
55 | }
56 |
--------------------------------------------------------------------------------
/src/ConnectionAwareTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 15.03.2016
10 | * Time: 20:32
11 | */
12 |
13 | namespace Blast\Orm;
14 |
15 |
16 | trait ConnectionAwareTrait
17 | {
18 |
19 | /**
20 | * @var \Doctrine\DBAL\Connection|\Blast\Orm\Connection
21 | */
22 | private $connection = null;
23 |
24 | /**
25 | * Get current connection
26 | *
27 | * @return \Doctrine\DBAL\Driver\Connection|\Doctrine\DBAL\Connection|\Blast\Orm\Connection
28 | */
29 | public function getConnection()
30 | {
31 | if(null === $this->connection){
32 | $this->connection = ConnectionManager::getInstance()->get();
33 | }
34 | return $this->connection;
35 | }
36 |
37 | /**
38 | * @param \Doctrine\DBAL\Driver\Connection|\Blast\Orm\Connection|null $connection
39 | *
40 | * @return $this
41 | */
42 | public function setConnection($connection)
43 | {
44 | $this->connection = $connection;
45 | return $this;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/ConnectionManagerInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 25.01.2016
10 | * Time: 16:08
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Query\Events;
15 |
16 |
17 | use Blast\Orm\Query;
18 |
19 | class QueryBuilderEvent extends AbstractQueryEvent
20 | {
21 | /**
22 | * @var string
23 | */
24 | private $name;
25 |
26 | /**
27 | * @var Query
28 | */
29 | private $builder;
30 |
31 | /**
32 | * ResultEvent constructor.
33 | * @param string $name
34 | * @param Query $builder
35 | */
36 | public function __construct($name, $builder)
37 | {
38 | $this->name = $name;
39 | $this->builder = $builder;
40 | }
41 |
42 | /**
43 | * Return event name
44 | * @return string
45 | */
46 | public function getName()
47 | {
48 | return $this->name;
49 | }
50 |
51 | /**
52 | * @return Query
53 | */
54 | public function getBuilder()
55 | {
56 | return $this->builder;
57 | }
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blast/orm",
3 | "description": "Framework agnostic data access and persistence based on Doctrine 2 DBAL",
4 | "keywords": [
5 | "sql",
6 | "database",
7 | "mapper",
8 | "repository",
9 | "entity",
10 | "orm",
11 | "query"
12 | ],
13 | "homepage": "https://github.com/phpthinktank/blast-orm",
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Marco Bunge",
18 | "email": "mjls@web.de",
19 | "homepage": "http://www.marco-bunge.com",
20 | "role": "Developer"
21 | }
22 | ],
23 | "require": {
24 | "php": "^5.5 || ^7.0",
25 | "doctrine/dbal": "2.5.*",
26 | "doctrine/inflector": "1.*",
27 | "doctrine/cache": "1.*",
28 | "league/event": "2.*",
29 | "minime/annotations": "^2.3",
30 | "zendframework/zend-hydrator": "2.*"
31 | },
32 | "require-dev": {
33 | "phpunit/phpunit": "4.*"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "Blast\\Orm\\": "src"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "Blast\\Tests\\Orm\\": "tests"
43 | }
44 | },
45 | "scripts": {
46 | "test": [
47 | "cd vendor/phpunit/phpunit",
48 | "phpunit --configuration phpunit.xml"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Query/Events/QueryResultEvent.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 25.01.2016
10 | * Time: 16:08
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Query\Events;
15 |
16 |
17 | class QueryResultEvent extends AbstractQueryEvent
18 | {
19 | /**
20 | * @var string
21 | */
22 | private $name;
23 |
24 | /**
25 | * @var boolean|array
26 | */
27 | private $result;
28 |
29 | /**
30 | * ResultEvent constructor.
31 | * @param string $name
32 | * @param boolean|array $result
33 | */
34 | public function __construct($name, $result)
35 | {
36 | $this->name = $name;
37 | $this->result = $result;
38 | }
39 |
40 | /**
41 | * Return event name
42 | * @return string
43 | */
44 | public function getName()
45 | {
46 | return $this->name;
47 | }
48 |
49 | /**
50 | * @return array|bool
51 | */
52 | public function getResult()
53 | {
54 | return $this->result;
55 | }
56 |
57 | /**
58 | * @param array|bool $result
59 | */
60 | public function setResult($result)
61 | {
62 | $this->result = $result;
63 | }
64 |
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/phpthinktank/blast-orm).
6 |
7 | ## Pull Requests
8 |
9 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
10 |
11 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
12 |
13 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
14 |
15 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
16 |
17 | - **Create feature branches** - Don't ask us to pull from your master branch.
18 |
19 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
20 |
21 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
22 |
23 | ## Install
24 |
25 | ``` bash
26 | $ composer install --dev
27 | ```
28 |
29 | ## Running Tests
30 |
31 | ``` bash
32 | $ composer test
33 | ```
34 |
35 | **Happy coding**!
36 |
--------------------------------------------------------------------------------
/src/Relations/RelationTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 12:06
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 |
17 | use Blast\Orm\Entity\ProviderFactoryInterface;
18 | use Blast\Orm\Query;
19 |
20 | trait RelationTrait
21 | {
22 |
23 | /**
24 | * @var Query
25 | */
26 | protected $query = null;
27 |
28 | /**
29 | * @var string
30 | */
31 | protected $name = null;
32 |
33 | /**
34 | * Query for accessing related data
35 | *
36 | * @return Query
37 | */
38 | abstract public function getQuery();
39 |
40 | /**
41 | * Get local entity
42 | *
43 | * @return mixed
44 | */
45 | abstract protected function getEntity();
46 |
47 | /**
48 | * Get foreign entity
49 | *
50 | * @return mixed
51 | */
52 | abstract protected function getForeignEntity();
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getName()
58 | {
59 | if($this instanceof ProviderFactoryInterface){
60 | $this->name = $this->createProvider($this->getForeignEntity())->getDefinition()->getTableName();
61 | }
62 | return $this->name;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/EventEmitterFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 12:09
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | interface EventEmitterFactoryInterface
18 | {
19 | /**
20 | * Create event emitter and set optional events as array or \League\Event\ListenerProviderInterface.
21 | *
22 | * Event array could have a name-handler-pair, a listener provider as instance of
23 | * `\League\Event\ListenerProviderInterface` or a argument array with name, handler and priority
24 | *
25 | * ```
26 | * $events = [
27 | * // name-handler-pair
28 | * 'eventName' => function(){},
29 | *
30 | * // listener provider as instance of \League\Event\ListenerProviderInterface
31 | * new \Acme\MyListenerProvider
32 | *
33 | * // argument array name, handler, prio
34 | * ['name', function(){}, 10]
35 | *
36 | * // alternating argument array name => [handler, prio]
37 | * 'eventName' => [function(){}, 10]
38 | *
39 | * ];
40 | * ```
41 | *
42 | * @param array $events
43 | *
44 | * @return \League\Event\Emitter
45 | */
46 | public function createEventEmitter($events = []);
47 | }
48 |
--------------------------------------------------------------------------------
/src/GatewayInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 13.04.2016
10 | * Time: 17:08
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 | /**
17 | * The gateway is accessing a single table or view and interacts with database
18 | *
19 | * @package Blast\Orm
20 | */
21 | interface GatewayInterface
22 | {
23 |
24 | /**
25 | * Create a new gateway for a single table
26 | *
27 | * @param $table
28 | */
29 | public function __construct($table);
30 |
31 | /**
32 | * Prepare insert statement
33 | *
34 | * @param $data
35 | * @param \Doctrine\DBAL\Schema\Column[] $fields
36 | *
37 | * @return $this
38 | */
39 | public function insert($data, $fields = []);
40 |
41 | /**
42 | * Prepare update statement
43 | *
44 | * @param $primaryKeyName
45 | * @param $data
46 | * @param \Doctrine\DBAL\Schema\Column[] $fields
47 | *
48 | * @return mixed
49 | */
50 | public function update($primaryKeyName, $data, $fields = []);
51 |
52 | /**
53 | * Prepare delete statement
54 | *
55 | * @param $primaryKeyName
56 | * @param $primaryKey
57 | *
58 | * @return mixed
59 | */
60 | public function delete($primaryKeyName, $primaryKey);
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Entity/DefinitionInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 08:08
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 | use Blast\Orm\MapperAwareInterface;
17 | use Blast\Orm\Relations\RelationsAwareInterface;
18 | use League\Event\EmitterAwareInterface;
19 |
20 | /**
21 | * All entity definition relating to schema definition
22 | *
23 | * @package Blast\Orm\Entity
24 | */
25 | interface DefinitionInterface extends EmitterAwareInterface, MapperAwareInterface, RelationsAwareInterface
26 | {
27 | /**
28 | * @return \ArrayObject|object
29 | */
30 | public function getEntity();
31 |
32 | /**
33 | * @return \SplStack|object
34 | */
35 | public function getEntityCollection();
36 |
37 | /**
38 | * @return \Doctrine\DBAL\Schema\Column[]
39 | */
40 | public function getFields();
41 |
42 | /**
43 | * @return \Doctrine\DBAL\Schema\Index[]
44 | */
45 | public function getIndexes();
46 |
47 | /**
48 | * Name of primary key
49 | *
50 | * @return string
51 | */
52 | public function getPrimaryKeyName();
53 |
54 | /**
55 | * Table name
56 | *
57 | * @param bool $withPrefix
58 | * @return string
59 | */
60 | public function getTableName($withPrefix = true);
61 | }
62 |
--------------------------------------------------------------------------------
/src/MapperInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 19.03.2016
10 | * Time: 12:44
11 | */
12 |
13 | namespace Blast\Orm;
14 |
15 | use Blast\Orm\Hydrator\HydratorInterface;
16 |
17 | abstract class AbstractRepository implements MapperFactoryInterface, RepositoryInterface
18 | {
19 |
20 | use MapperFactoryTrait;
21 |
22 | /**
23 | * Get entity for repository
24 | *
25 | * @return object
26 | */
27 | abstract public function getEntity();
28 |
29 | /**
30 | * Get a collection of all entities
31 | *
32 | * @return \SplStack|array
33 | */
34 | public function all()
35 | {
36 | return $this->createMapper($this->getEntity())->select()->execute(HydratorInterface::HYDRATE_COLLECTION);
37 | }
38 |
39 | /**
40 | * Find entity by primary key
41 | *
42 | * @param mixed $primaryKey
43 | * @return \ArrayObject|\stdClass|object
44 | */
45 | public function find($primaryKey)
46 | {
47 | return $this->createMapper($this->getEntity())->find($primaryKey)->execute(HydratorInterface::HYDRATE_ENTITY);
48 | }
49 |
50 | /**
51 | * Save new or existing entity data
52 | *
53 | * @param object|array $data
54 | * @return int|bool
55 | */
56 | public function save($data)
57 | {
58 | return $this->createMapper($data)->save($data)->execute();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Support.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 27.07.2016
10 | * Time: 11:07
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | use Doctrine\Common\Cache\Cache;
18 |
19 | class Support
20 | {
21 |
22 | /**
23 | * Get a list of PHP defined classes
24 | *
25 | * @param $nameOrObject
26 | * @return bool
27 | */
28 | public static function isPHPInternalClass($nameOrObject){
29 | return in_array(ltrim((string)static::getClass($nameOrObject), '\\'), spl_classes());
30 | }
31 |
32 | /**
33 | * Get class of given FQCN or Object
34 | *
35 | * @param $nameOrObject
36 | * @return bool|string
37 | */
38 | public static function getClass($nameOrObject){
39 | if(is_string($nameOrObject) && class_exists($nameOrObject)){
40 | return $nameOrObject;
41 | }
42 |
43 | if(is_object($nameOrObject)){
44 | return get_class($nameOrObject);
45 | }
46 |
47 | return false;
48 | }
49 |
50 | /**
51 | * @param $entity
52 | * @param Cache $cache
53 | * @return \ReflectionClass
54 | */
55 | public static function getCachedReflectionClass($entity, Cache $cache)
56 | {
57 | $entityFQCN = static::getClass($entity);
58 | $isPHPInternalClass = static::isPHPInternalClass($entityFQCN);
59 |
60 | // load from cache if entity is a valid and no internal php class
61 | if(false !== $entityFQCN && false === $isPHPInternalClass){
62 | if($cache->contains($entityFQCN)){
63 | return $cache->fetch($entityFQCN);
64 | }
65 | }
66 |
67 | $reflection = new \ReflectionClass($entity);
68 |
69 | // avoid caching if entity is an internal php class
70 | if(true === $isPHPInternalClass){
71 | return $reflection;
72 | }
73 |
74 | $cache->save($entityFQCN, $reflection);
75 |
76 | return $cache->fetch($entityFQCN);
77 | }
78 |
79 | /**
80 | * @param $entity
81 | * @return bool|string
82 | */
83 | public static function getEntityName($entity){
84 | $fqcn = static::getClass($entity);
85 | return false === static::isPHPInternalClass($fqcn) && false !== $fqcn ?
86 | $fqcn : false;
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/EventEmitterFactoryTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 11:27
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | use League\Event\Emitter;
18 | use League\Event\EmitterInterface;
19 | use League\Event\ListenerAcceptorInterface;
20 | use League\Event\ListenerInterface;
21 | use League\Event\ListenerProviderInterface;
22 |
23 | trait EventEmitterFactoryTrait
24 | {
25 |
26 | /**
27 | * Create event emitter and set optional events as array or \League\Event\ListenerProviderInterface.
28 | * Set as second argument a emitter instance, otherwise the factory creates a new one.
29 | *
30 | * Event array could have a name-handler-pair, a listener provider as instance of
31 | * `\League\Event\ListenerProviderInterface` or a argument array with name, handler and priority
32 | *
33 | * Configure event array as follows:
34 | *
35 | * ```
36 | * $events = [
37 | * // name-handler-pair
38 | * 'eventName' => function(){},
39 | *
40 | * // listener provider as instance of \League\Event\ListenerProviderInterface
41 | * new \Acme\MyListenerProvider
42 | *
43 | * // argument array name, handler, prio
44 | * ['name', function(){}, 10]
45 | *
46 | * // alternating argument array name => [handler, prio]
47 | * 'eventName' => [function(){}, 10]
48 | *
49 | * ];
50 | * ```
51 | *
52 | * @param \League\Event\ListenerProviderInterface|array $events
53 | * @param \League\Event\EmitterInterface $emitter
54 | *
55 | * @return Emitter
56 | */
57 | public function createEventEmitter($events = [], EmitterInterface $emitter = null)
58 | {
59 | if(null === $emitter){
60 | $emitter = new Emitter();
61 | }
62 |
63 | if (!is_array($events)) {
64 | $events = [$events];
65 | }
66 |
67 | if (!empty($events)) {
68 | foreach ($events as $name => $handler) {
69 |
70 | if ($handler instanceof ListenerProviderInterface) {
71 | $emitter->useListenerProvider($handler);
72 | continue;
73 | }
74 |
75 | $args = is_array($handler) ?
76 | array_merge([$name], $handler) :
77 | [$name, $handler, EmitterInterface::P_NORMAL];
78 |
79 | call_user_func_array([$emitter, 'addListener'], $args);
80 | }
81 | }
82 |
83 | return $emitter;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/Relations/BelongsTo.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 12:04
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 |
17 | use Blast\Orm\ConnectionAwareInterface;
18 | use Blast\Orm\ConnectionAwareTrait;
19 | use Blast\Orm\Entity\EntityAwareTrait;
20 | use Blast\Orm\Entity\ProviderFactoryInterface;
21 | use Blast\Orm\Entity\ProviderFactoryTrait;
22 | use Blast\Orm\Hydrator\HydratorInterface;
23 | use Blast\Orm\Query;
24 | use Doctrine\Common\Inflector\Inflector;
25 |
26 | class BelongsTo implements ConnectionAwareInterface, RelationInterface, ProviderFactoryInterface
27 | {
28 | use ConnectionAwareTrait;
29 | use EntityAwareTrait;
30 | use ProviderFactoryTrait;
31 | use RelationTrait;
32 |
33 | /**
34 | * @var
35 | */
36 | private $foreignEntity;
37 | /**
38 | * @var null
39 | */
40 | private $localKey;
41 |
42 | /**
43 | * Local entity belongs to foreign entity by local key
44 | *
45 | * @param $entity
46 | * @param $foreignEntity
47 | * @param null $localKey
48 | */
49 | public function __construct($entity, $foreignEntity, $localKey = null)
50 | {
51 | $this->entity = $entity;
52 | $this->foreignEntity = $foreignEntity;
53 | $this->localKey = $localKey;
54 | }
55 |
56 | /**
57 | * @return array|\ArrayObject|object|bool
58 | */
59 | public function execute()
60 | {
61 | return $this->getQuery()->execute(HydratorInterface::HYDRATE_AUTO);
62 | }
63 |
64 | /**
65 | * Get relation query
66 | *
67 | * @return \Blast\Orm\Query
68 | */
69 | public function getQuery()
70 | {
71 | if(null !== $this->query){
72 | return $this->query;
73 | }
74 | $provider = $this->createProvider($this->getEntity());
75 | $foreignProvider = $this->createProvider($this->getForeignEntity());
76 | $localKey = $this->getLocalKey();
77 |
78 | if ($localKey === null) {
79 | $localKey = Inflector::singularize($foreignProvider->getDefinition()->getTableName()) . '_' . $foreignProvider->getDefinition()->getPrimaryKeyName();
80 | }
81 |
82 | $data = $provider->extract();
83 |
84 | //find primary key
85 | $primaryKey = $data[$localKey];
86 |
87 | $mapper = $foreignProvider->getDefinition()->getMapper()->setConnection($this->getConnection());
88 |
89 | //if no primary key is available, return a select
90 | $this->query = $primaryKey === null ? $mapper->select() : $mapper->find($primaryKey);
91 |
92 | return $this->query;
93 | }
94 |
95 | /**
96 | * @return mixed
97 | */
98 | public function getForeignEntity()
99 | {
100 | return $this->foreignEntity;
101 | }
102 |
103 | /**
104 | * @return null
105 | */
106 | public function getLocalKey()
107 | {
108 | return $this->localKey;
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/Gateway.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 13.04.2016
10 | * Time: 17:23
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | use Blast\Orm\Relations\RelationInterface;
18 | use Doctrine\DBAL\Types\Type;
19 |
20 | class Gateway implements GatewayInterface, ConnectionAwareInterface
21 | {
22 |
23 | use ConnectionAwareTrait;
24 |
25 | /**
26 | * @var string
27 | */
28 | private $table;
29 |
30 | /**
31 | * Create a new gateway for a single table
32 | *
33 | * @param $table
34 | */
35 | public function __construct($table)
36 | {
37 | $this->table = $table;
38 | }
39 |
40 | /**
41 | * Prepare insert statement
42 | *
43 | * @param $data
44 | *
45 | * @param \Doctrine\DBAL\Schema\Column[] $fields
46 | * @return Query|bool
47 | */
48 | public function insert($data, $fields = [])
49 | {
50 | //cancel if $data has no entries
51 | if (count($data) < 1) {
52 | return false;
53 | }
54 |
55 | //prepare statement
56 | $query = $this->getConnection()->createQuery();
57 | $query->insert($this->table);
58 | $this->addDataToQuery($data, $fields, $query);
59 |
60 | return $query;
61 | }
62 |
63 | /**
64 | * Prepare update statement
65 | *
66 | * @param $primaryKeyName
67 | * @param $data
68 | * @param \Doctrine\DBAL\Schema\Column[] $fields
69 | *
70 | * @return mixed
71 | */
72 | public function update($primaryKeyName, $data, $fields = [])
73 | {
74 | //prepare statement
75 | $query = $this->getConnection()->createQuery();
76 | $query->update($this->table);
77 |
78 | $this->addDataToQuery($data, $fields, $query);
79 |
80 | return $query->where($query->expr()->eq($primaryKeyName, $data[$primaryKeyName]));
81 | }
82 |
83 | /**
84 | * Prepare delete statement
85 | *
86 | * @param $primaryKeyName
87 | * @param $primaryKey
88 | *
89 | * @return mixed
90 | */
91 | public function delete($primaryKeyName, $primaryKey)
92 | {
93 | $query = $this->getConnection()->createQuery();
94 | $query
95 | ->delete($this->table)
96 | ->where($query->expr()->eq($primaryKeyName, $query->createPositionalParameter($primaryKey)));
97 |
98 | return $query;
99 |
100 | }
101 |
102 | /**
103 | *
104 | * @todo determine exclusion from gateway and integration into query similar to php value convert
105 | *
106 | * @param $data
107 | * @param \Doctrine\DBAL\Schema\Column[] $fields
108 | * @param Query $query
109 | */
110 | protected function addDataToQuery($data, $fields, Query $query)
111 | {
112 | foreach ($data as $key => $value) {
113 | $query->addColumnValue($key, $query->createPositionalParameter(
114 | $value, array_key_exists($key, $fields) ?
115 | $fields[$key]->getType()->getName() :
116 | Type::STRING));
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Relations/HasMany.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 12:14
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 | use Blast\Orm\ConnectionAwareInterface;
17 | use Blast\Orm\ConnectionAwareTrait;
18 | use Blast\Orm\Entity\EntityAwareTrait;
19 | use Blast\Orm\Entity\Provider;
20 | use Blast\Orm\Entity\ProviderFactoryInterface;
21 | use Blast\Orm\Entity\ProviderFactoryTrait;
22 | use Blast\Orm\Hydrator\HydratorInterface;
23 | use Blast\Orm\Query;
24 | use Doctrine\Common\Inflector\Inflector;
25 |
26 | class HasMany implements ConnectionAwareInterface, ProviderFactoryInterface, RelationInterface
27 | {
28 |
29 | use ConnectionAwareTrait;
30 | use EntityAwareTrait;
31 | use ProviderFactoryTrait;
32 | use RelationTrait;
33 |
34 | /**
35 | * @var
36 | */
37 | private $foreignEntity;
38 | /**
39 | * @var null
40 | */
41 | private $foreignKey;
42 |
43 | /**
44 | * Local entity relates to many entries of foreign entity by foreign key
45 | *
46 | * @param $entity
47 | * @param $foreignEntity
48 | * @param null $foreignKey
49 | */
50 | public function __construct($entity, $foreignEntity, $foreignKey = null)
51 | {
52 | $this->entity = $entity;
53 | $this->foreignEntity = $foreignEntity;
54 | $this->foreignKey = $foreignKey;
55 | }
56 |
57 | /**
58 | * @return \\ArrayObject
59 | */
60 | public function execute()
61 | {
62 | return $this->getQuery()->execute(HydratorInterface::HYDRATE_COLLECTION);
63 | }
64 |
65 | /**
66 | * Get relation query
67 | *
68 | * @return \Blast\Orm\Query
69 | */
70 | public function getQuery()
71 | {
72 | if(null !== $this->query){
73 | return $this->query;
74 | }
75 | $provider = $this->createProvider($this->getEntity());
76 | $foreignProvider = $this->createProvider($this->getForeignEntity());
77 | $foreignKey = $this->getForeignKey();
78 |
79 | $data = $provider->extract();
80 |
81 | //find primary key
82 | if ($foreignKey === null) {
83 | $foreignKey = Inflector::singularize($provider->getDefinition()->getTableName()) . '_' . $provider->getDefinition()->getPrimaryKeyName();
84 | }
85 |
86 | $mapper = $foreignProvider->getDefinition()->getMapper()->setConnection($this->getConnection());
87 |
88 | $foreignKeyValue = isset($data[$provider->getDefinition()->getPrimaryKeyName()]) ? $data[$provider->getDefinition()->getPrimaryKeyName()] : false;
89 |
90 | //if no primary key is available, return a select
91 | $query = $mapper->select();
92 | if ($foreignKeyValue !== false) {
93 | $query->where((new Query($this->getConnection()))->expr()->eq($foreignKey, $foreignKeyValue));
94 | }
95 | $this->query = $query;
96 |
97 | return $this->query;
98 | }
99 |
100 | /**
101 | * @return mixed
102 | */
103 | public function getForeignEntity()
104 | {
105 | return $this->foreignEntity;
106 | }
107 |
108 | /**
109 | * @return null
110 | */
111 | public function getForeignKey()
112 | {
113 | return $this->foreignKey;
114 | }
115 |
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/Connection.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 13.04.2016
10 | * Time: 08:20
11 | *
12 | */
13 |
14 | namespace Blast\Orm;
15 |
16 |
17 | use Doctrine\Common\Cache\ArrayCache;
18 | use Doctrine\Common\Cache\Cache;
19 | use Doctrine\Common\EventManager;
20 | use Doctrine\DBAL\Configuration;
21 | use Doctrine\DBAL\Connection as DbalConnection;
22 | use Doctrine\DBAL\Driver;
23 | use Doctrine\DBAL\Query\QueryBuilder;
24 |
25 | /**
26 | * The connection wrapper provides connection-aware mapper and query based on current connection
27 | *
28 | *
29 | * @package Blast\Orm
30 | */
31 | class Connection extends DbalConnection implements MapperFactoryInterface, QueryFactoryInterface
32 | {
33 |
34 | /**
35 | * Table name prefix for connections
36 | * @var null|string
37 | */
38 | private $prefix = null;
39 |
40 | /**
41 | *
42 | * @var null|\Doctrine\Common\Cache\Cache
43 | */
44 | private $internalCache = null;
45 |
46 | /**
47 | *
48 | * @var null|\Doctrine\Common\Cache\Cache
49 | */
50 | private $metaDataCache = null;
51 |
52 |
53 | /**
54 | *
55 | * @var null|\Doctrine\Common\Cache\Cache
56 | */
57 | private $reflectionCache = null;
58 |
59 | use MapperFactoryTrait {
60 | createMapper as protected internalCreateMapper;
61 | }
62 |
63 | public function __construct(array $params, Driver $driver, $config, $eventManager)
64 | {
65 | parent::__construct($params, $driver, $config, $eventManager);
66 | }
67 |
68 |
69 | /**
70 | * Factory method for create a new Mapper for given entity.
71 | *
72 | * * ```php
73 | *
74 | * //create mapper from connection
75 | * $connection->createMapper(Post::class);
76 | *
77 | * ```
78 | *
79 | * @param $entity
80 | *
81 | * @return Mapper
82 | */
83 | public function createMapper($entity){
84 | return $this->internalCreateMapper($entity, $this);
85 | }
86 |
87 | /**
88 | * Factory method for create a new query for given entity with optional custom query builder.
89 | *
90 | * @param $entity
91 | *
92 | * @param \Doctrine\DBAL\Query\QueryBuilder $builder
93 | *
94 | * @return \Blast\Orm\Query
95 | */
96 | public function createQuery($entity = null, QueryBuilder $builder = null)
97 | {
98 | $query = new Query($this, $entity);
99 | $query->setBuilder(null === $builder ? parent::createQueryBuilder() : $builder);
100 | return $query;
101 | }
102 |
103 | /**
104 | * @return null|string
105 | */
106 | public function getPrefix()
107 | {
108 | return $this->prefix;
109 | }
110 |
111 | /**
112 | * @param null|string $prefix
113 | * @return $this
114 | */
115 | public function setPrefix($prefix)
116 | {
117 | $this->prefix = $prefix;
118 | return $this;
119 | }
120 |
121 | private function getInternalCache()
122 | {
123 | if (null === $this->internalCache) {
124 | $this->internalCache = new ArrayCache();
125 | }
126 |
127 | return $this->internalCache;
128 | }
129 |
130 | /**
131 | * @return Cache|null
132 | */
133 | public function getMetaDataCache()
134 | {
135 | if(null === $this->metaDataCache){
136 | $cache = $this->getInternalCache();
137 | $this->metaDataCache = clone $cache;
138 | }
139 | return $this->metaDataCache;
140 | }
141 |
142 | /**
143 | * @return Cache|null
144 | */
145 | public function getReflectionCache()
146 | {
147 | if(null === $this->reflectionCache){
148 | $cache = $this->getInternalCache();
149 | $this->reflectionCache = clone $cache;
150 | }
151 | return $this->reflectionCache;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/src/Entity/Provider.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 01.03.2016
10 | * Time: 09:33
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 |
17 | use Blast\Orm\CacheAwareTrait;
18 | use Blast\Orm\Hydrator\EntityHydrator;
19 | use Blast\Orm\Hydrator\HydratorInterface;
20 | use Blast\Orm\Support;
21 | use Doctrine\Common\Inflector\Inflector;
22 |
23 | class Provider implements ProviderInterface
24 | {
25 |
26 | use CacheAwareTrait;
27 | use EntityAwareTrait;
28 |
29 | /**
30 | * Entity definition
31 | *
32 | * @var DefinitionInterface
33 | */
34 | private $definition;
35 |
36 | /**
37 | * Provider constructor.
38 | *
39 | * @param $tableName
40 | */
41 | public function __construct($tableName)
42 | {
43 | $transformer = $transformer = $this->transform($tableName);
44 |
45 | $this->entity = $transformer->getEntity();
46 | $this->definition = $transformer->getDefinition();
47 | }
48 |
49 | /**
50 | * @return DefinitionInterface
51 | */
52 | public function getDefinition()
53 | {
54 | return $this->definition;
55 | }
56 |
57 | /**
58 | * Convert data array to entity with data
59 | *
60 | * @param array $data
61 | * @param string $option
62 | * @return object|\ArrayObject
63 | */
64 | public function hydrate(array $data = [], $option = HydratorInterface::HYDRATE_AUTO)
65 | {
66 | return (new EntityHydrator($this))->hydrate($data, $option);
67 | }
68 |
69 | /**
70 | * Convert object properties or object getter to data array
71 | *
72 | * @param array $additionalData
73 | * @return array|\ArrayObject
74 | */
75 | public function extract(array $additionalData = [])
76 | {
77 | return array_replace_recursive($additionalData, (new EntityHydrator($this))->extract());
78 | }
79 |
80 | /**
81 | * Check if entity is new or not
82 | *
83 | * @return bool
84 | */
85 | public function isNew()
86 | {
87 | $data = $this->extract();
88 |
89 | return isset($data[$this->getDefinition()->getPrimaryKeyName()]) ? empty($data[$this->getDefinition()->getPrimaryKeyName()]) : true;
90 | }
91 |
92 | /**
93 | * Todo rewrite this horrobile piece of code...
94 | * @param $tableName
95 | * @return bool|string
96 | */
97 | private function determineCacheId($tableName)
98 | {
99 | /** @var string|bool $compTableName */
100 | if ($tableName instanceof DefinitionInterface) {
101 | return $tableName->getTableName();
102 | }
103 | if (null === $tableName) {
104 | return false;
105 | }
106 | if (is_string($tableName)) {
107 | return
108 | (class_exists($tableName) && false === Support::isPHPInternalClass($tableName))
109 | ? Inflector::pluralize(Inflector::tableize(Support::getCachedReflectionClass($tableName, $this->getReflectionCache())->getShortName()))
110 | : $tableName;
111 | }
112 | if (is_array($tableName)) {
113 | return false;
114 | }
115 | if(Support::isPHPInternalClass($tableName)){
116 | return false;
117 | }
118 | if ($tableName instanceof EntityAwareInterface) {
119 | $compTableName = Inflector::pluralize(Inflector::tableize(Support::getCachedReflectionClass(Support::getEntityName($tableName->getEntity()), $this->getReflectionCache())->getShortName()));
120 | if (is_string($compTableName)) {
121 | return $compTableName;
122 | }
123 | }
124 | if (is_object($tableName)) {
125 | $compTableName = Inflector::pluralize(Inflector::tableize(Support::getCachedReflectionClass(Support::getEntityName($tableName), $this->getReflectionCache())->getShortName()));
126 |
127 | if (is_string($compTableName)) {
128 | return $compTableName;
129 | }
130 | }
131 |
132 | return false;
133 |
134 | }
135 |
136 | /**
137 | * @param $tableName
138 | * @return Transformer
139 | */
140 | private function transform($tableName)
141 | {
142 | $transformer = new Transformer();
143 | $transformer->transform($tableName);
144 | return $transformer;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/ConnectionManager.php:
--------------------------------------------------------------------------------
1 | $definition];
80 | }
81 |
82 | if (!is_array($definition)) {
83 | throw new DBALException('Unable to determine parameter array from definition');
84 | }
85 |
86 | if (!array_key_exists('wrapperClass', $definition)) {
87 | $definition['wrapperClass'] = ConnectionWrapper::class;
88 | }
89 |
90 | $connection = DriverManager::getConnection($definition);
91 |
92 | if (!($connection instanceof Connection)) {
93 | throw new \RuntimeException(sprintf('Connection needs to be an instance of %s', Connection::class));
94 | }
95 |
96 | //setup special configuration for blast connections
97 | if ($connection instanceof \Blast\Orm\Connection) {
98 | if (array_key_exists('prefix', $definition)) {
99 | $connection->setPrefix($definition['prefix']);
100 | }
101 | }
102 |
103 | return $connection;
104 | }
105 |
106 | /**
107 | * Close all connections on
108 | */
109 | public function __destruct()
110 | {
111 | $this->closeAll();
112 | }
113 |
114 | /**
115 | * Disconnect all connections and remove all
116 | * connections. Collect garbage at least.
117 | */
118 | public function closeAll()
119 | {
120 | $connections = $this->all();
121 |
122 | foreach ($connections as $connection) {
123 | if ($connection->isConnected()) {
124 | $connection->close();
125 | }
126 | }
127 |
128 | $this->connections = [];
129 | gc_collect_cycles();
130 |
131 | }
132 |
133 | /**
134 | * Get all connections
135 | *
136 | * @return \Doctrine\DBAL\Connection[]|\Blast\Orm\Connection[]
137 | */
138 | public function all()
139 | {
140 | return $this->connections;
141 | }
142 |
143 | /**
144 | * Add a new connection to internal cache. Create connection
145 | * with `Blast\Orm\ConnectionManager::create`
146 | *
147 | * @see http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#getting-a-connection
148 | *
149 | * @param array|\Doctrine\DBAL\Connection|string $connection
150 | * @param string $name
151 | *
152 | * @return $this
153 | *
154 | * @throws \Doctrine\DBAL\DBALException
155 | */
156 | public function add($connection, $name = self::DEFAULT_CONNECTION)
157 | {
158 | if ($this->has($name)) {
159 | throw new DBALException(sprintf('Connection with name %s already exists!', $name));
160 | }
161 |
162 | $this->connections[$name] = static::create($connection);
163 |
164 | //set first connection as active connection
165 | if (count($this->connections) === 1) {
166 | $this->swapActiveConnection($name);
167 | }
168 |
169 | return $this;
170 | }
171 |
172 | /**
173 | * Check if connections exists
174 | *
175 | * @param $name
176 | * @return bool
177 | */
178 | public function has($name)
179 | {
180 | return isset($this->connections[$name]);
181 | }
182 |
183 | /**
184 | * Swap current connection with another connection
185 | * by name and add previous connection to previous
186 | * connection stack.
187 | *
188 | * @param string $name
189 | *
190 | * @return $this
191 | *
192 | * @throws \Doctrine\DBAL\DBALException
193 | */
194 | public function swapActiveConnection($name)
195 | {
196 | if (!$this->has($name)) {
197 | throw new DBALException(sprintf('Connection with name %s not found!', $name));
198 | }
199 |
200 | if ($this->defaultConnection !== null) {
201 | $this->previousConnections[] = $this->defaultConnection;
202 | }
203 | $this->defaultConnection = $this->get($name);
204 |
205 | return $this;
206 | }
207 |
208 | /**
209 | * Get connection by name.
210 | *
211 | * @param $name
212 | *
213 | * @return \Doctrine\DBAL\Connection|\Blast\Orm\Connection
214 | *
215 | * @throws \Doctrine\DBAL\DBALException
216 | */
217 | public function get($name = null)
218 | {
219 | if ($name === null) {
220 | return $this->defaultConnection;
221 | }
222 | if ($this->has($name)) {
223 | return $this->connections[$name];
224 | }
225 |
226 | throw new DBALException('Unknown connection ' . $name);
227 | }
228 |
229 | /**
230 | * @return array
231 | */
232 | public function getPrevious()
233 | {
234 | return $this->previousConnections;
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/Relations/ManyToMany.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 29.02.2016
10 | * Time: 16:45
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Relations;
15 |
16 |
17 | use Blast\Orm\ConnectionAwareInterface;
18 | use Blast\Orm\ConnectionAwareTrait;
19 | use Blast\Orm\Entity\Definition;
20 | use Blast\Orm\Entity\EntityAwareTrait;
21 | use Blast\Orm\Entity\ProviderFactoryInterface;
22 | use Blast\Orm\Entity\ProviderFactoryTrait;
23 | use Blast\Orm\Hydrator\HydratorInterface;
24 | use Blast\Orm\Query;
25 | use Doctrine\Common\Util\Inflector;
26 |
27 | class ManyToMany implements ConnectionAwareInterface, ProviderFactoryInterface, RelationInterface
28 | {
29 | use ConnectionAwareTrait;
30 | use EntityAwareTrait;
31 | use ProviderFactoryTrait;
32 | use RelationTrait;
33 |
34 | /**
35 | * @var object|string
36 | */
37 | private $foreignEntity;
38 | /**
39 | * @var null|string
40 | */
41 | private $foreignKey;
42 | /**
43 | * @var null|string
44 | */
45 | private $localKey;
46 | /**
47 | * @var null|object|string
48 | */
49 | private $junction;
50 | /**
51 | * @var null|string
52 | */
53 | private $junctionLocalKey;
54 | /**
55 | * @var null|string
56 | */
57 | private $junctionForeignKey;
58 |
59 |
60 | /**
61 | * Many occurrences in local entity relate to many occurrences in foreign entity and vice versa.
62 | * The relations are linked by a junction table.
63 | *
64 | * @param string|object $entity
65 | * @param string|object $foreignEntity
66 | * @param null|string $foreignKey Default field name is {foreign primary key name}
67 | * @param null|string $localKey Default field name is {local primary key name}
68 | * @param null|string|object $junction Default table name is {local entity table name}_{foreign entity table name}
69 | * @param null|string $junctionLocalKey Default field name is {local table name}_{$localKey}
70 | * @param null|string $junctionForeignKey Default field name is {foreign table name}_{$foreignKey}
71 | */
72 | public function __construct($entity, $foreignEntity, $foreignKey = null, $localKey = null,
73 | $junction = null, $junctionLocalKey = null, $junctionForeignKey = null)
74 | {
75 |
76 | $this->entity = $entity;
77 | $this->foreignEntity = $foreignEntity;
78 | $this->foreignKey = $foreignKey;
79 | $this->localKey = $localKey;
80 | $this->junction = $junction;
81 | $this->junctionLocalKey = $junctionLocalKey;
82 | $this->junctionForeignKey = $junctionForeignKey;
83 | }
84 |
85 | /**
86 | * @return null|string
87 | */
88 | public function getLocalKey()
89 | {
90 | return $this->localKey;
91 | }
92 |
93 | /**
94 | * @return \SplStack
95 | */
96 | public function execute()
97 | {
98 | return $this->getQuery()->execute(HydratorInterface::HYDRATE_COLLECTION);
99 | }
100 |
101 | /**
102 | * Get relation query
103 | *
104 | * @return \Blast\Orm\Query
105 | */
106 | public function getQuery()
107 | {
108 | if (null !== $this->query) {
109 | return $this->query;
110 | }
111 | $provider = $this->createProvider($this->getEntity());
112 | $foreignProvider = $this->createProvider($this->getForeignEntity());
113 | $foreignKey = $this->getForeignKey();
114 | $junction = $this->getJunction();
115 | $junctionLocalKey = $this->getJunctionLocalKey();
116 | $junctionForeignKey = $this->getJunctionForeignKey();
117 |
118 | $data = $provider->extract();
119 |
120 | $localKey = $provider->getDefinition()->getPrimaryKeyName();
121 |
122 | //determine foreign key
123 | if ($foreignKey === null) {
124 | $foreignKey = $foreignProvider->getDefinition()->getPrimaryKeyName();
125 | }
126 |
127 | //determine through
128 | if (!is_string($junction) || $junction === null) {
129 | $junction = Inflector::singularize($provider->getDefinition()->getTableName()) . '_' . Inflector::singularize($foreignProvider->getDefinition()->getTableName());
130 | }
131 |
132 | //determine through local key
133 | if ($junctionLocalKey === null) {
134 | $junctionLocalKey = Inflector::singularize($provider->getDefinition()->getTableName()) . '_' . $localKey;
135 | }
136 |
137 | //determine through foreign key
138 | if ($junctionForeignKey === null) {
139 | $junctionForeignKey = Inflector::singularize($foreignProvider->getDefinition()->getTableName()) . '_' . $foreignKey;
140 | }
141 |
142 | $query = new Query($provider->getDefinition()->getMapper()->getConnection());
143 |
144 | //prepare query for foreign table
145 | $foreignQuery = $foreignProvider->getDefinition()->getMapper()
146 | ->setConnection($this->getConnection())
147 | ->select();
148 |
149 | //get relations by through db object
150 | if (isset($data[$localKey])) {
151 | $junctionProvider = is_string($junction) ?
152 | $this->createProvider($junction) :
153 | $junction;
154 | $junctionMapper = $junctionProvider->getDefinition()->getMapper();
155 | $junctionMapper->setConnection($this->getConnection());
156 | if(true){
157 |
158 | }
159 | $results = $junctionMapper
160 | ->select([$junctionForeignKey])
161 | ->where($query->expr()->eq($junctionLocalKey, $data[$localKey]))
162 | ->execute(HydratorInterface::HYDRATE_RAW);
163 |
164 | //set conditions on foreign query
165 | foreach ($results as $result) {
166 | $foreignQuery->where($query->expr()->eq($foreignKey, $result[$junctionForeignKey]));
167 | }
168 | }
169 |
170 | $this->query = $foreignQuery;
171 |
172 | return $this->query;
173 | }
174 |
175 | /**
176 | * @return object|string
177 | */
178 | public function getForeignEntity()
179 | {
180 | return $this->foreignEntity;
181 | }
182 |
183 | /**
184 | * @return null|string
185 | */
186 | public function getForeignKey()
187 | {
188 | return $this->foreignKey;
189 | }
190 |
191 | /**
192 | * @return null|object|string
193 | */
194 | public function getJunction()
195 | {
196 | return $this->junction;
197 | }
198 |
199 | /**
200 | * @return null|string
201 | */
202 | public function getJunctionLocalKey()
203 | {
204 | return $this->junctionLocalKey;
205 | }
206 |
207 | /**
208 | * @return null|string
209 | */
210 | public function getJunctionForeignKey()
211 | {
212 | return $this->junctionForeignKey;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/ChangeLog-1.0.md:
--------------------------------------------------------------------------------
1 | # Changes in Blast orm 1.0
2 |
3 | All notable changes of the Blast orm 1.0 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
4 |
5 | ## 0.6.9
6 |
7 | ### Fixed
8 |
9 | - Relations ignored while determining field data automatically
10 |
11 | ## 0.6.8
12 |
13 | ### Fixed
14 |
15 | - Fix assigning relations. Conflicts between camelcase and underscore are resolved.
16 |
17 | ## 0.6.7
18 |
19 | ### Altered
20 |
21 | - Transforming of fields from entity to definition is respecting defined fields and automatically declaring columns of
22 | type string and with original property name of entity for undefined fields
23 | - Hydrator extraction respects field names and entity names and accurate assignment of field names to data.
24 |
25 | ### Fixed
26 |
27 | - underscorized fields are now correctly assigned their camelcased mutators and accessors in entity
28 |
29 | ## 0.6.6
30 |
31 | ### Altered
32 |
33 | - Allow relations to replace their entity via `Blast\Orm\Entity\EntityAwareInterface`
34 |
35 | ### Fixed
36 |
37 | - Fix empty entities in relations when entities relation definition is used
38 |
39 | ## 0.6.5
40 |
41 | ### Altered
42 |
43 | - avoid plurilize for table name defined via property or method in entity
44 |
45 | ## 0.6.4
46 |
47 | ### Altered
48 |
49 | - Fix typo and return type
50 | - Refactor adding data to query statement
51 |
52 | ### Added
53 |
54 | - Add table prefixes when accessing tablename from provider
55 | - Add accessor for definitions in entities
56 | - Add array caching backend for entity reflection and entity meta data
57 |
58 | ## 0.6.3
59 |
60 | ### Altered
61 |
62 | - `\Blast\Orm\Provider` additional data extraction is now working correctly
63 | - Update Readme
64 |
65 | ## 0.6.2
66 |
67 | ### Added
68 |
69 | - add `\Blast\Orm\Gateway` as accessor of a single database table or view
70 |
71 | ## 0.6.1
72 |
73 | ### Added
74 |
75 | - `\Blast\Orm\QueryFactoryInterface` act as contract for query creation
76 | - `\Blast\Orm\Connection` acts as connection wrapper for doctrine connection and is able to create connection-aware mappers and queries. createQueryBuilder is still creating doctrine SQL query builder!
77 |
78 | ### Altered
79 |
80 | - `\Blast\Orm\MapperFactoryInterface` is not forcing to add a connection
81 |
82 | ## 0.6.0
83 |
84 | ### Altered
85 |
86 | - add `\Blast\Orm\Entity\DefinitionInterface` and use definition instead of provider
87 | - Refactor hydrators use [`zendframework/zend-hydrator`](http://framework.zend.com/manual/current/en/modules/zend.stdlib.hydrator.html) for internal logic.
88 |
89 | ### Removed
90 |
91 | - remove definition data getters from provider, you need to use `$provider->getDefiniton()->get*()`
92 |
93 | ## 0.5.3
94 |
95 | ### Altered
96 |
97 | - Upgrade minimum requirements and dependecies
98 |
99 | ## 0.5.2
100 |
101 | ### Altered
102 |
103 | - Consider fields
104 | - Convert result from sql select to php value
105 | - Convert values from sql update or create to sql value
106 |
107 | ## 0.5.1
108 |
109 | ### Fixes
110 |
111 | - Fix bug: Use configured collection from definition instead of always \SplStack
112 |
113 | ### Altered
114 |
115 | - Add definition tests for mapper and query
116 | - Rename query `before` event to `build`
117 | - Rename query `after` event to `result`
118 | - Update readme
119 |
120 | ## 0.5
121 |
122 | ### Fixes
123 |
124 | - Fix bug where plain object don't get data from hydrator
125 | - Fix bug throwing exception when attaching relation without name
126 |
127 | ### Added
128 |
129 | - `Blast\Orm\Entity\Transformer` converts any configuration into definition and entity
130 | - `Blast\Orm\Entity\Definition` is holding entity definitions.
131 |
132 | ### Altered
133 |
134 | - connection manager is now accessible as singleton via `Blast\Orm\ConnectionManager::getInstance()`
135 | - Relations don't need to initialize to get relation name
136 | - Relation internals refactored
137 | - `\Blast\Orm\Hydrator\ArrayToObjectHydrator` hydrates relations as well as data.
138 | - Get pluralized table names when table name was suggested from entity shortname
139 | - `Blast\Orm\Entity\Provider` uses definition and transformer for preparing entities
140 | - update abstract repository with new provider and mapper
141 |
142 | ### Removed
143 |
144 | - `Blast\Orm\Facades`
145 | - Locator and Container no longer dependencies for blast orm
146 |
147 | ## 0.4
148 |
149 | ### Added
150 |
151 | - `Blast\Orm\Locator` deliver methods to access providers, mappers and connections and replaces `Blast\Orm\Entity\EntityAdapterCollectionFacade` and `Blast\Orm\ConnectionCollectionFacade`
152 | - `Blast\Orm\LocatorFacade` solves IoC concerns by providing swappable locator from container.
153 | - `Blast\Orm\Entity\Provider` replaces entity adaption
154 | - `Blast\Orm\Hydrator` manages hydration from object to array and vice versa
155 |
156 | ### Altered
157 |
158 | - `Blast\Orm\Data\DataAdapter` is now delivering logic to call data
159 | - `Blast\Orm\Entity\EntityAdapter` is now delivering logic to call definitions
160 | - Rename `Blast\Orm\Entity\EntityAdapterCollection` to `Blast\Orm\Entity\EntityAdapterManager`
161 | - Connection manager simplify redundant method names. Removed `Configuration` word from `get, set, getPrevious`, `getConnections` becomes `all`
162 | - Connection initiation to mapper or query
163 | - Add query event classes
164 |
165 | ### Removed
166 |
167 | - Replace `Blast\Orm\Container` with `League\Container`
168 | - `Blast\Orm\Entity\Provider` replaces entity adapter classes and definition interfaces
169 | - `Blast\Orm\Object\ObjectAdapter`
170 | - `Blast\Orm\ConnectionCollectionFacade`
171 | - `Blast\Orm\Query\Result`
172 | - `Blast\Orm\Entity\GenericEntity`
173 | - `Blast\Orm\Query\ResultInterface`
174 | - `Blast\Orm\Data`
175 | - `Blast\Orm\Hook`
176 |
177 | ## 0.3
178 |
179 | ### Added
180 |
181 | - `Blast\Orm\Mapper` for accessing and persisting data
182 | - `Blast\Orm\Relations` component to create relations between entities
183 | - `Blast\Orm\Object\ObjectAdapterCache` for reusing entity definition
184 | - `Blast\Orm\Facades` for more customizable classes against contracts
185 |
186 | ### Altered
187 |
188 | - `Blast\Orm\Repository` is mediating between entity and mapper delivered by entity, repository queries are excluded to mappers
189 |
190 | ### Removed
191 |
192 | - `Blast\Orm\Repository::create`
193 | - `Blast\Orm\Repository::delete`
194 | - `Blast\Orm\Repository::update`
195 | - `Blast\Orm\Manager` has been replaced by ConnectionFacade
196 |
197 | ## 0.2
198 |
199 | ### Added
200 |
201 | - `Blast\Orm\Data` component for convenient data access
202 | - `Blast\Orm\Repository` mediates between entities and query
203 | - `Blast\Orm\Hook` is providing a low-level Observer-Subject implementation basically for extending logic of class methods in `Blast\Orm\Data`
204 |
205 | ### Altered
206 |
207 | - Rename `Blast\Db`to `Blast\Orm\`
208 | - `Blast\Orm\Query::execute` is now able to emit before and after on execution by type name
209 | - `Blast\Orm\Entity` component is providing adapters for accessing entity classes and determine definition and data
210 |
211 | ### Removed
212 |
213 | - `Blast\Db\Entity` component
214 | - `Blast\Db\Orm` component (replaced by repository)
215 |
--------------------------------------------------------------------------------
/src/Entity/Transformer.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 08:05
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 | use Blast\Orm\CacheAwareTrait;
17 | use Blast\Orm\Support;
18 | use Doctrine\Common\Inflector\Inflector;
19 |
20 | class Transformer implements TransformerInterface, EntityAwareInterface
21 | {
22 |
23 | use EntityAwareTrait;
24 | use CacheAwareTrait;
25 |
26 | /**
27 | * @var \Blast\Orm\Entity\DefinitionInterface
28 | */
29 | private $definition = null;
30 |
31 | /**
32 | * Transform configuration into entity and entity definition. Configuration could be a
33 | * string (class name or table name), array (convert to a definition),
34 | * a Definition instance or an Entity instance.
35 | *
36 | * @param $configuration
37 | * @return mixed
38 | */
39 | public function transform($configuration)
40 | {
41 | if($configuration instanceof DefinitionInterface){
42 | $this->definition = $configuration;
43 | $this->setEntity($this->getDefinition()->getEntity());
44 | return $this;
45 | }
46 | if(null === $configuration){
47 | $configuration = \ArrayObject::class;
48 | }
49 | if (is_string($configuration)) {
50 | $configuration = $this->transformStringToDefinitionArray($configuration);
51 | }
52 | if (is_array($configuration)) {
53 | $this->definition = $this->transformArrayToDefinition($configuration);
54 | $this->setEntity($this->getDefinition()->getEntity());
55 |
56 | return $this;
57 | }
58 | if ($configuration instanceof EntityAwareInterface) {
59 | $this->setEntity($configuration->getEntity());
60 | $this->definition = $this->transformEntityToDefinition($this->getEntity());
61 | }
62 | if (is_object($configuration)) {
63 | $this->setEntity($configuration);
64 | $this->definition = $this->transformEntityToDefinition($this->getEntity());
65 | }
66 |
67 | return $this;
68 |
69 | }
70 |
71 | /**
72 | * @return DefinitionInterface
73 | */
74 | public function getDefinition()
75 | {
76 | return $this->definition;
77 | }
78 |
79 | private function transformStringToDefinitionArray($configuration)
80 | {
81 | $tableOrClass = $configuration;
82 | $configuration = [];
83 |
84 | if (class_exists($tableOrClass)) {
85 | $configuration['entity'] = new $tableOrClass;
86 | $configuration['tableName'] = $tableOrClass;
87 | }else{
88 | $configuration['tableName'] = $tableOrClass;
89 | }
90 |
91 | return $configuration;
92 | }
93 |
94 | /**
95 | * @param $configuration
96 | * @return Definition
97 | */
98 | private function transformArrayToDefinition($configuration)
99 | {
100 | $definition = (new Definition())->setConfiguration($configuration);
101 | // transform entity to definition and overwrite configuration
102 | return $this->transformEntityToDefinition($definition->getEntity(), $definition);
103 | }
104 |
105 | /**
106 | * @param $entity
107 | * @param \Blast\Orm\Entity\DefinitionInterface|null $definition
108 | * @return Definition
109 | */
110 | private function transformEntityToDefinition($entity, $definition = null)
111 | {
112 | // find definition class in entity by property or method
113 | $definition = $this->loadDefinitionFromEntity($entity, $definition);
114 |
115 | $reflection = Support::getCachedReflectionClass($entity, $this->getReflectionCache());
116 | $configuration = $definition->getConfiguration();
117 |
118 | // mapper is a dependency for all other entity services
119 | // fetch mapper first
120 | if ($reflection->hasMethod('mapper')) {
121 | $mapperMethod = $reflection->getMethod('mapper');
122 | if($mapperMethod->isStatic() && $mapperMethod->isPublic()){
123 | $configuration['mapper'] = $mapperMethod->invokeArgs($entity, [$entity]);
124 | }
125 | }
126 |
127 | //update configuration with mapper
128 | $configuration = $definition->setConfiguration($configuration)->getConfiguration();
129 |
130 | foreach ($configuration as $key => $value) {
131 | //ignore entity or mapper
132 | if (in_array($key, ['entity', 'mapper'])) {
133 | continue;
134 | }
135 | if ($reflection->hasMethod($key)) {
136 | $method = $reflection->getMethod($key);
137 | if ($method->isPublic() && $method->isStatic()) {
138 | $value = $method->invokeArgs($entity, [$entity, $definition->getMapper()]);
139 | }
140 | } else {
141 | $value = is_callable($value) ?
142 | call_user_func_array($value, [$entity, $definition->getMapper()]) :
143 | $value;
144 | }
145 | $configuration[$key] = $value;
146 | }
147 |
148 | $configuration['entityClassName'] = $reflection->getName();
149 | $configuration['entityShortName'] = $reflection->getShortName();
150 |
151 | // fetch table name from table property
152 | if($reflection->hasProperty('table')){
153 | $reflectionProperty = $reflection->getProperty('table');
154 | if($reflectionProperty->isStatic()){
155 | $configuration['tableName'] = $reflectionProperty->getValue($entity);
156 | }
157 | }
158 |
159 | // fetch table name from table method
160 | if($reflection->hasMethod('table')){
161 | $reflectionMethod = $reflection->getMethod('table');
162 | if($reflectionMethod->isStatic()){
163 | $configuration['tableName'] = $reflectionMethod->invoke($entity);
164 | }
165 | }
166 |
167 | $definition->setConfiguration($configuration);
168 |
169 | // set table name from reflection short name
170 | // if table name is unknown or FQCN and if
171 | // entity is no ArrayObject
172 | if ((empty($definition->getTableName()) || class_exists($definition->getTableName()) ) && !($definition->getEntity() instanceof \ArrayObject)) {
173 | $configuration['tableName'] = Inflector::pluralize(Inflector::tableize($configuration['entityShortName']));
174 | }
175 |
176 | return $definition->setConfiguration($configuration);
177 | }
178 |
179 | /**
180 | * @param $entity
181 | * @param Definition|null $definition
182 | * @return Definition|null
183 | */
184 | private function loadDefinitionFromEntity($entity, $definition = null)
185 | {
186 | $definitionClass = null;
187 | if (property_exists($entity, 'definition')) {
188 | $definitionClass = $entity::$definition;
189 | }
190 | if (method_exists($entity, 'definition')) {
191 | $definitionClass = $entity::definition();
192 | }
193 | if (null !== $definitionClass) {
194 | $definition = is_object($definitionClass) ? $definitionClass : new $definitionClass;
195 | }
196 | if (null === $definition) {
197 | $definition = new Definition();
198 | }
199 |
200 | $configuration = $definition->getConfiguration();
201 | $configuration['entity'] = $entity;
202 | $definition->setConfiguration($configuration);
203 |
204 | return $definition;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/Hydrator/EntityHydrator.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 11.03.2016
10 | * Time: 23:24
11 | */
12 |
13 | namespace Blast\Orm\Hydrator;
14 |
15 | use Blast\Orm\Entity\ProviderInterface;
16 | use Doctrine\Common\Inflector\Inflector;
17 | use Doctrine\DBAL\Driver\Statement;
18 | use Zend\Hydrator\ArraySerializable;
19 | use Zend\Hydrator\ClassMethods;
20 | use Zend\Hydrator\ObjectProperty;
21 |
22 | class EntityHydrator implements HydratorInterface
23 | {
24 |
25 | /**
26 | * @var \Blast\Orm\Entity\Provider
27 | */
28 | private $provider;
29 |
30 | public function __construct(ProviderInterface $provider)
31 | {
32 | $this->provider = $provider;
33 | }
34 |
35 | /**
36 | * Extract values from given object
37 | *
38 | * @return array
39 | */
40 | public function extract()
41 | {
42 | $entity = clone $this->provider->getEntity();
43 |
44 | // extract from array object
45 | // ignore further extraction strategy
46 | if ( method_exists($entity, 'populate') || method_exists($entity, 'exchangeArray') ) {
47 | $hydrator = $this->getArraySerizableHydrator();
48 | return $this->underscorizeKeys($hydrator->extract($entity));
49 | }
50 |
51 | // extract data from class getters and properties
52 | $propertyHydrator = $this->getObjectPropertyHydrator();
53 | $classMethodHydrator = $this->getClassMethodsHydrator();
54 |
55 | // get a full set of original names, camelize keys and underscrorize keys
56 | $extractedDataSet = array_replace(
57 | $propertyHydrator->extract($entity),
58 | $classMethodHydrator->extract($entity),
59 | $this->camelizeKeys($propertyHydrator->extract($entity)),
60 | $this->camelizeKeys($classMethodHydrator->extract($entity)),
61 | $this->underscorizeKeys($propertyHydrator->extract($entity)),
62 | $this->underscorizeKeys($classMethodHydrator->extract($entity))
63 | );
64 |
65 | $fields = $this->provider->getDefinition()->getFields();
66 | $extractedData = [];
67 |
68 | foreach ($extractedDataSet as $key => $value){
69 | if(array_key_exists($key, $fields)){
70 | $extractedData[$key] = $value;
71 | }
72 | }
73 |
74 | return $extractedData;
75 | }
76 |
77 | /**
78 | * @param array $data
79 | * @param string $option
80 | * @return mixed
81 | */
82 | public function hydrate($data = [], $option = self::HYDRATE_AUTO)
83 | {
84 | $option = $this->determineOption($data, $option);
85 |
86 | switch ($option) {
87 | case self::HYDRATE_RAW:
88 | return $data;
89 | //if entity set has many items, return a collection of entities
90 | case self::HYDRATE_COLLECTION :
91 | return $this->hydrateCollection($data);
92 | //if entity has one item, return the entity
93 | case self::HYDRATE_ENTITY:
94 | $data = $this->isCollectable($data) ? array_shift($data) : $data;
95 |
96 | return $this->hydrateEntity($data);
97 | }
98 |
99 | throw new \LogicException('Unknown option ' . $option);
100 | }
101 |
102 | /**
103 | * @param $data
104 | * @return mixed
105 | */
106 | public function isCollectable($data)
107 | {
108 | if ( ! is_array($data) ) {
109 | return false;
110 | }
111 |
112 | return is_array(reset($data));
113 | }
114 |
115 | /**
116 | * @param $data
117 | * @param $option
118 | * @return string
119 | */
120 | protected function determineOption($data, $option)
121 | {
122 | if ( $option === self::HYDRATE_RAW ||
123 | $data instanceof Statement ||
124 | is_scalar($data) ||
125 | is_bool($data) ||
126 | null === $data
127 | ) {
128 | return self::HYDRATE_RAW;
129 | }
130 | if ( $option === self::HYDRATE_AUTO ) {
131 | $option = $this->isCollectable($data) && (count($data) === 0 || count($data) > 1) ? self::HYDRATE_COLLECTION : self::HYDRATE_ENTITY;
132 | }
133 |
134 | return $option;
135 | }
136 |
137 | /**
138 | * @param $data
139 | * @return object|\SplStack
140 | */
141 | protected function hydrateCollection($data)
142 | {
143 | $stack = $this->provider->getDefinition()->getEntityCollection();
144 | foreach ($data as $key => $value) {
145 | $stack->push($this->hydrateEntity($value));
146 | }
147 |
148 | $stack->rewind();
149 |
150 | return $stack;
151 | }
152 |
153 | /**
154 | * Hydrates data to an entity
155 | *
156 | * @param $data
157 | * @return array|\ArrayObject|object|\stdClass
158 | */
159 | protected function hydrateEntity($data)
160 | {
161 | $entity = clone $this->provider->getEntity();
162 |
163 | // hydrate to array object
164 | if ( method_exists($entity, 'populate') || method_exists($entity, 'exchangeArray') ) {
165 | $hydrator = $this->getArraySerizableHydrator();
166 | $entity = $hydrator->hydrate($data, $entity);
167 |
168 | //add relations
169 | $data = $this->addRelationsToData($data, $entity);
170 | $entity = $hydrator->hydrate($data, $entity);
171 | }
172 |
173 | $data = $this->camelizeKeys($data);
174 |
175 | $classMethodHydrator = $this->getClassMethodsHydrator();
176 | $propertyHydrator = $this->getObjectPropertyHydrator();
177 |
178 | // hydrate entity data
179 | $entity = $classMethodHydrator->hydrate(
180 | $data,
181 | $propertyHydrator->hydrate($data, $entity)
182 | );
183 |
184 | //add relations
185 | $data = $this->addRelationsToData($data, $entity);
186 |
187 | // hydrate entity with relation data to add hydrated entity to relation
188 | return $classMethodHydrator->hydrate(
189 | $data,
190 | $propertyHydrator->hydrate($data, $entity)
191 | );
192 | }
193 |
194 | /**
195 | * Add relations to data
196 | *
197 | * @param $data
198 | * @param $entity
199 | * @return mixed
200 | */
201 | protected function addRelationsToData($data, $entity)
202 | {
203 | foreach ($this->provider->getDefinition()->getRelations() as $name => $relation) {
204 | if ( is_numeric($name) ) {
205 | $name = $relation->getName();
206 | }
207 | // disallow overwriting existing data
208 | if ( isset($data[$name]) ) {
209 | continue;
210 | }
211 |
212 | // only attached entity is allowed!
213 | $entityClass = get_class($entity);
214 | if($relation->getEntity() instanceof $entityClass){
215 | $relation->setEntity($entity);
216 | }
217 | $data[$name] = $relation;
218 | $data[Inflector::camelize($name)] = $relation;
219 | $data[Inflector::tableize($name)] = $relation;
220 | }
221 |
222 | return $data;
223 | }
224 |
225 | /**
226 | * Convert keys to camel case
227 | *
228 | * @param $data
229 | * @return array
230 | */
231 | protected function camelizeKeys($data)
232 | {
233 | $result = [];
234 | foreach ($data as $key => $value) {
235 | $result[Inflector::camelize($key)] = $value;
236 | }
237 |
238 | return $result;
239 | }
240 |
241 | /**
242 | * Convert keys to underscore
243 | *
244 | * @param $data
245 | * @return array
246 | */
247 | protected function underscorizeKeys($data)
248 | {
249 | $result = [];
250 | foreach ($data as $key => $value) {
251 | $result[Inflector::tableize($key)] = $value;
252 | }
253 |
254 | return $result;
255 | }
256 |
257 | /**
258 | * @return ArraySerializable
259 | */
260 | protected function getArraySerizableHydrator()
261 | {
262 | return new ArraySerializable();
263 | }
264 |
265 | /**
266 | * @return ClassMethods
267 | */
268 | protected function getClassMethodsHydrator()
269 | {
270 | return new ClassMethods(false);
271 | }
272 |
273 | /**
274 | * @return ObjectProperty
275 | */
276 | protected function getObjectPropertyHydrator()
277 | {
278 | $hydrator = new ObjectProperty();
279 |
280 | return $hydrator;
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/src/Entity/Definition.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 17.03.2016
10 | * Time: 08:17
11 | *
12 | */
13 |
14 | namespace Blast\Orm\Entity;
15 |
16 |
17 | use Blast\Orm\ConnectionManager;
18 | use Blast\Orm\EventEmitterFactoryInterface;
19 | use Blast\Orm\EventEmitterFactoryTrait;
20 | use Blast\Orm\Mapper;
21 | use Blast\Orm\MapperFactoryInterface;
22 | use Blast\Orm\MapperFactoryTrait;
23 | use Blast\Orm\MapperInterface;
24 | use Blast\Orm\Relations\RelationInterface;
25 | use Doctrine\Common\Inflector\Inflector;
26 | use Doctrine\DBAL\Schema\Column;
27 | use Doctrine\DBAL\Types\Type;
28 | use League\Event\EmitterAwareInterface;
29 | use League\Event\EmitterAwareTrait;
30 |
31 | class Definition implements DefinitionInterface, EventEmitterFactoryInterface, MapperFactoryInterface
32 | {
33 |
34 | use EmitterAwareTrait;
35 | use EventEmitterFactoryTrait;
36 | use MapperFactoryTrait;
37 |
38 | private $configuration = [
39 | 'entity' => \ArrayObject::class,
40 | 'entityCollection' => \SplStack::class,
41 | 'events' => [],
42 | 'fields' => [],
43 | 'indexes' => [],
44 | 'primaryKeyName' => ProviderInterface::DEFAULT_PRIMARY_KEY_NAME,
45 | 'tableName' => '',
46 | 'mapper' => Mapper::class,
47 | 'relations' => [],
48 | ];
49 |
50 | /**
51 | * @return array
52 | */
53 | public function getConfiguration()
54 | {
55 | return $this->configuration;
56 | }
57 |
58 | /**
59 | * Add additional configuration. Configuration will be merged into
60 | *
61 | * @param array $configuration
62 | * @return Definition
63 | */
64 | public function setConfiguration(array $configuration)
65 | {
66 | $this->mergeConfiguration($configuration);
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * Get the entity object.
73 | *
74 | * @return \ArrayObject|object
75 | */
76 | public function getEntity()
77 | {
78 | if (!is_object($this->configuration['entity'])) {
79 | $entity = $this->configuration['entity'];
80 | $this->configuration['entity'] = new $entity;
81 | }
82 | return $this->configuration['entity'];
83 | }
84 |
85 | /**
86 | * Get the custom entity collection
87 | *
88 | * @return \SplStack|object
89 | */
90 | public function getEntityCollection()
91 | {
92 | if (!is_object($this->configuration['entityCollection'])) {
93 | $entity = $this->configuration['entityCollection'];
94 | $this->configuration['entityCollection'] = new $entity;
95 | }
96 | return $this->configuration['entityCollection'];
97 | }
98 |
99 | /**
100 | * Load event emitter. If entity has events and
101 | * no emitter exists, create a new emitter.
102 | *
103 | * @return \ArrayObject|object
104 | */
105 | public function getEmitter()
106 | {
107 | if (null === $this->emitter) {
108 | if (!empty($this->configuration['events'])) {
109 | $entity = $this->getEntity();
110 | if ($entity instanceof EmitterAwareInterface) {
111 | $emitter = $this->createEventEmitter($this->configuration['events'], $entity->getEmitter());
112 | } else {
113 | $emitter = $this->createEventEmitter($this->configuration['events']);
114 | }
115 | } else {
116 | $emitter = $this->createEventEmitter();
117 | }
118 |
119 | $this->setEmitter($emitter);
120 | }
121 | return $this->emitter;
122 | }
123 |
124 | /**
125 | * Get fields
126 | *
127 | * @return \Doctrine\DBAL\Schema\Column[]
128 | */
129 | public function getFields()
130 | {
131 | return $this->configuration['fields'];
132 | }
133 |
134 | /**
135 | * Get indexes
136 | *
137 | * @return \Doctrine\DBAL\Schema\Index[]
138 | */
139 | public function getIndexes()
140 | {
141 | return $this->configuration['indexes'];
142 | }
143 |
144 | /**
145 | * Get name of primary key
146 | *
147 | * @return string
148 | */
149 | public function getPrimaryKeyName()
150 | {
151 | return $this->configuration['primaryKeyName'];
152 | }
153 |
154 | /**
155 | * Get table name
156 | *
157 | * Add prefix if if $withPrefix is true and a prefix exists
158 | *
159 | * @param bool $withPrefix
160 | * @return string
161 | */
162 | public function getTableName($withPrefix = true)
163 | {
164 | $prefix = ConnectionManager::getInstance()->get()->getPrefix();
165 | return (true === $withPrefix && strlen($prefix) > 0 ? rtrim($prefix, '_') . '_' : '') . $this->configuration['tableName'];
166 | }
167 |
168 | /**
169 | * Get entity mapper
170 | *
171 | * @return MapperInterface
172 | */
173 | public function getMapper()
174 | {
175 | if (!($this->configuration['mapper'] instanceof MapperInterface)) {
176 | $this->configuration['mapper'] = $this->createMapper($this);
177 | }
178 | return $this->configuration['mapper'];
179 | }
180 |
181 | /**
182 | * Get an array of relations
183 | *
184 | * @return RelationInterface[]
185 | */
186 | public function getRelations()
187 | {
188 | return $this->configuration['relations'];
189 | }
190 |
191 | /**
192 | * Merge partial configuration into definition configuration. Normalize partial
193 | * configuration keys before add them to configuration. Add custom configuration
194 | * after adding known configuration.
195 | *
196 | * @param array $configuration
197 | *
198 | * @return $this
199 | */
200 | private function mergeConfiguration(array $configuration)
201 | {
202 | $originalConfiguration = $configuration;
203 |
204 | // normalize keys to lower case
205 | foreach ($configuration as $key => $value) {
206 | $configuration[strtolower($key)] = $value;
207 | }
208 |
209 | // add definition configuration by it's real key
210 | $configKeys = array_keys($this->configuration);
211 | foreach ($configKeys as $key) {
212 | $normalizedKey = strtolower($key);
213 | if (isset($configuration[$normalizedKey])) {
214 | $this->configuration[$key] = $configuration[$normalizedKey];
215 | unset($configuration[$normalizedKey]);
216 | }
217 | }
218 |
219 | // add custom configuration by it's original key
220 | foreach ($originalConfiguration as $key => $value) {
221 | $normalizedKey = strtolower($key);
222 | if (isset($configuration[$normalizedKey])) {
223 | $this->configuration[$key] = $originalConfiguration[$key];
224 | }
225 | }
226 |
227 | $this->processFields();
228 |
229 | return $this;
230 | }
231 |
232 | /**
233 | * Setup entity fields and determine undefined fields from entity with type string
234 | */
235 | private function processFields()
236 | {
237 | /** @var Column[] $internalFields */
238 | $internalFields = $this->configuration['fields'];
239 | $fields = [];
240 |
241 | foreach ($internalFields as $name => $field){
242 | // skip if field is no instance of Column
243 | if(!($field instanceof Column)){
244 | continue;
245 | }
246 |
247 | //set table name as key
248 | if($field->getName() !== $name){
249 | $name = $field->getName();
250 | }
251 |
252 | $fields[$name] = $field;
253 | }
254 |
255 | $reflectionClass = new \ReflectionClass($this->getEntity());
256 | $entityProperties = $reflectionClass->getProperties();
257 |
258 | foreach ($entityProperties as $key => $value){
259 | // disallow static properties
260 | if($value->isStatic()){
261 | continue;
262 | }
263 |
264 | // skip dynamic column declaration
265 | $fieldName = $value->getName();
266 | if(
267 | array_key_exists($fieldName, $fields)
268 | || array_key_exists(Inflector::camelize($fieldName), $fields)
269 | || array_key_exists(Inflector::tableize($fieldName), $fields)
270 | ){
271 | continue;
272 | }
273 |
274 | $fields[$fieldName] = new Column($fieldName, Type::getType(Type::STRING));
275 |
276 | }
277 |
278 | //remove all relations
279 | $relations = $this->getRelations();
280 | foreach($fields as $key => $value){
281 | if(array_key_exists($key, $relations)){
282 | unset($fields[$key]);
283 | }
284 | }
285 |
286 | $this->configuration['fields'] = $fields;
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/src/Mapper.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
61 | if ( $entity instanceof DefinitionInterface ) {
62 | $this->setEntity($entity->getEntity());
63 | $this->definition = $entity;
64 | } elseif ( $entity instanceof ProviderInterface ) {
65 | $this->setEntity($entity->getEntity());
66 | $this->provider = $entity;
67 | } else {
68 | $this->setEntity($entity);
69 | }
70 | }
71 |
72 | /**
73 | * Select query for finding entity by primary key
74 | *
75 | * @param mixed $primaryKey
76 | * @return Query
77 | */
78 | public function find($primaryKey)
79 | {
80 | $query = $this->select();
81 | $query->where($query->expr()->eq(
82 | $this->getDefinition()->getPrimaryKeyName(),
83 | $query->createPositionalParameter($primaryKey))
84 | );
85 |
86 | return $query;
87 | }
88 |
89 | /**
90 | * Select query for entity
91 | *
92 | * @param array $selects
93 | * @return Query
94 | */
95 | public function select($selects = ['*'])
96 | {
97 | return $this->createQuery()
98 | ->select($selects)
99 | ->from($this->getDefinition()->getTableName());
100 | }
101 |
102 | /**
103 | * Create a new Query instance
104 | * @return \Blast\Orm\Query
105 | */
106 | public function createQuery()
107 | {
108 | return new Query($this->getConnection(), $this->getEntity());
109 | }
110 |
111 | /**
112 | * Create a new Query instance
113 | * @return \Blast\Orm\Gateway
114 | */
115 | public function createGateway($tableName)
116 | {
117 | $gateway = new Gateway($tableName);
118 | $gateway->setConnection($this->getConnection());
119 |
120 | return $gateway;
121 | }
122 |
123 | /**
124 | * @return ProviderInterface
125 | */
126 | public function getProvider()
127 | {
128 | if ( null === $this->provider ) {
129 | $this->provider = $this->createProvider($this->getEntity());
130 | }
131 |
132 | return $this->provider;
133 | }
134 |
135 | /**
136 | * @return DefinitionInterface
137 | */
138 | public function getDefinition()
139 | {
140 | if ( null === $this->definition ) {
141 | $this->definition = $this->createProvider($this->getEntity())->getDefinition();
142 | }
143 |
144 | return $this->definition;
145 | }
146 |
147 | /**
148 | * Create query for new entity.
149 | *
150 | * @param array|\ArrayObject|\stdClass|object $entity
151 | * @return Query|bool
152 | */
153 | public function create($entity)
154 | {
155 | //load entity provider
156 | $provider = $this->prepareProvider($entity);
157 | $definition = $provider->getDefinition();
158 |
159 | //disallow differing entities
160 | return $this->checkEntity($definition->getEntity())
161 | ->createGateway($definition->getTableName())
162 | ->insert($provider->extract(), $definition->getFields());
163 | }
164 |
165 | /**
166 | * Update query for existing Model or a collection of entities in storage
167 | *
168 | * @param array|\ArrayObject|\stdClass|object $entity
169 | * @return Query
170 | */
171 | public function update($entity)
172 | {
173 | //load entity provider
174 | $provider = $this->prepareProvider($entity);
175 | $definition = $provider->getDefinition();
176 |
177 | //disallow differing entities
178 | return $this->checkEntity($definition->getEntity())
179 | ->createGateway($definition->getTableName())
180 | ->update(
181 | $definition->getPrimaryKeyName(),
182 | $provider->extract(),
183 | $definition->getFields()
184 | );
185 | }
186 |
187 | /**
188 | * Create or update an entity
189 | *
190 | * @param \ArrayObject|\SplStack|\stdClass|object $entity
191 | * @return Query
192 | */
193 | public function save($entity)
194 | {
195 | return $this->createProvider($entity)->isNew() ? $this->create($entity) : $this->update($entity);
196 | }
197 |
198 | /**
199 | * Prepare delete query for attached entity by identifiers
200 | *
201 | * @param int|string $identifier
202 | * @return query
203 | */
204 | public function delete($identifier)
205 | {
206 | $definition = $this->getDefinition();
207 |
208 | //prepare statement
209 | $primaryKeyName = $definition->getPrimaryKeyName();
210 |
211 | if ( is_object($identifier) ) {
212 | $identifierProvider = $this->createProvider($identifier);
213 | $this->checkEntity($identifierProvider->getEntity());
214 | $data = $identifierProvider->extract();
215 | $identifier = $data[$primaryKeyName];
216 | }
217 |
218 | return $this
219 | ->createGateway($definition->getTableName())
220 | ->delete($primaryKeyName, $identifier);
221 | }
222 |
223 | /**
224 | * BelongsTo is the inverse of a HasOne or a HasMany relation.
225 | *
226 | * One entity is associated with one related entity by a field which
227 | * associates with primary key in related entity.
228 | *
229 | * @param $entity
230 | * @param $foreignEntity
231 | * @param null $localKey
232 | *
233 | * @return Query
234 | */
235 | public function belongsTo($entity, $foreignEntity, $localKey = null)
236 | {
237 | return $this->prepareRelation(new BelongsTo($entity, $foreignEntity, $localKey));
238 | }
239 |
240 | /**
241 | * One entity is associated with one related entity by a field which
242 | * associates with primary key in current entity.
243 | *
244 | * @param $entity
245 | * @param $foreignEntity
246 | * @param null|string $foreignKey
247 | *
248 | * @return Query
249 | */
250 | public function hasOne($entity, $foreignEntity, $foreignKey = null)
251 | {
252 | return $this->prepareRelation(new HasOne($entity, $foreignEntity, $foreignKey));
253 | }
254 |
255 | /**
256 | * One entity is associated with many related entities
257 | * by a field which associates with primary key in current entity.
258 | *
259 | * @param $entity
260 | * @param $foreignEntity
261 | * @param null $foreignKey
262 | *
263 | * @return Query
264 | */
265 | public function hasMany($entity, $foreignEntity, $foreignKey = null)
266 | {
267 | return $this->prepareRelation(new HasMany($entity, $foreignEntity, $foreignKey));
268 | }
269 |
270 | /**
271 | * Many entities of type _A_ are associated with many
272 | * related entities of type _B_ by a junction table.
273 | * The junction table stores associations from entities
274 | * of type _A_ to entities of type _B_.
275 | *
276 | * @param $entity
277 | * @param $foreignEntity
278 | * @param null $foreignKey
279 | * @param null $localKey
280 | * @param null $junction
281 | * @param null $junctionLocalKey
282 | * @param null $junctionForeignKey
283 | *
284 | * @return Query
285 | */
286 | public function manyToMany(
287 | $entity,
288 | $foreignEntity,
289 | $foreignKey = null,
290 | $localKey = null,
291 | $junction = null,
292 | $junctionLocalKey = null,
293 | $junctionForeignKey = null
294 | ) {
295 |
296 | return $this->prepareRelation(
297 | new ManyToMany($entity, $foreignEntity, $foreignKey,
298 | $localKey, $junction, $junctionLocalKey, $junctionForeignKey)
299 | );
300 |
301 | }
302 |
303 | /**
304 | * Prepare provider by determining entity type
305 | *
306 | * @param $entity
307 | * @return Provider
308 | */
309 | private function prepareProvider($entity)
310 | {
311 | if ( is_array($entity) ) {
312 | $provider = $this->createProvider($this->getEntity());
313 |
314 | // reset entity in provider and
315 | // set data
316 | $provider->setEntity($provider->hydrate($entity, HydratorInterface::HYDRATE_ENTITY));
317 | } else {
318 | $provider = $this->createProvider($entity);
319 | }
320 |
321 | return $provider;
322 | }
323 |
324 | /**
325 | * Share mapper connection with relation
326 | *
327 | * @param $relation
328 | * @return mixed
329 | */
330 | private function prepareRelation(RelationInterface $relation)
331 | {
332 | if ( $relation instanceof ConnectionAwareInterface ) {
333 | $relation->setConnection($this->getConnection());
334 | }
335 |
336 | return $relation;
337 | }
338 |
339 | /**
340 | * Check if external entity matches mapper entity
341 | *
342 | * @param $entity
343 | *
344 | * @return $this
345 | *
346 | * @throws \InvalidArgumentException
347 | */
348 | private function checkEntity($entity)
349 | {
350 | if ( get_class($entity) !== get_class($this->getDefinition()->getEntity()) ) {
351 | throw new \InvalidArgumentException('Disallowed usage of differing entity!' . get_class($entity));
352 | }
353 |
354 | return $this;
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/src/Query.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE.txt
7 | * file that was distributed with this source code.
8 | *
9 | * Date: 08.02.2016
10 | * Time: 16:11
11 | */
12 |
13 | namespace Blast\Orm;
14 |
15 | use Blast\Orm\Entity\EntityAwareInterface;
16 | use Blast\Orm\Entity\EntityAwareTrait;
17 | use Blast\Orm\Entity\ProviderFactoryInterface;
18 | use Blast\Orm\Entity\ProviderFactoryTrait;
19 | use Blast\Orm\Entity\ProviderInterface;
20 | use Blast\Orm\Hydrator\EntityHydrator;
21 | use Blast\Orm\Hydrator\HydratorInterface;
22 | use Blast\Orm\Query\Events\QueryBuilderEvent;
23 | use Blast\Orm\Query\Events\QueryResultEvent;
24 | use Doctrine\DBAL\Query\QueryBuilder;
25 | use Doctrine\DBAL\Types\Type;
26 | use League\Event\EmitterAwareInterface;
27 | use League\Event\EmitterAwareTrait;
28 | use stdClass;
29 |
30 | /**
31 | * Class Query
32 | *
33 | * @method \Doctrine\DBAL\Query\Expression\ExpressionBuilder expr()
34 | * @method int getType()
35 | * @method int getState()
36 | * @method string getSQL()
37 | * @method Query setParameter($key, $value, $type = null)
38 | * @method Query setParameters(array $params, array $types = [])
39 | * @method array getParameters()
40 | * @method mixed getParameter($key)
41 | * @method array getParameterTypes()
42 | * @method mixed getParameterType($key)
43 | * @method Query setFirstResult($firstResult)
44 | * @method int getFirstResult()
45 | * @method Query setMaxResults($maxResults)
46 | * @method int getMaxResults()
47 | * @method Query add($sqlPartName, $sqlPart, $append = false)
48 | * @method Query addSelect($select = null)
49 | * @method Query delete($delete = null, $alias = null)
50 | * @method Query update($update = null, $alias = null)
51 | * @method Query insert($insert = null)
52 | * @method Query from($from, $alias = null)
53 | * @method Query join($fromAlias, $join, $alias, $condition = null)
54 | * @method Query innerJoin($fromAlias, $join, $alias, $condition = null)
55 | * @method Query leftJoin($fromAlias, $join, $alias, $condition = null)
56 | * @method Query rightJoin($fromAlias, $join, $alias, $condition = null)
57 | * @method Query set($key, $value)
58 | * @method Query where($predicates)
59 | * @method Query andWhere($where)
60 | * @method Query orWhere($where)
61 | * @method Query groupBy($groupBy)
62 | * @method Query addGroupBy($groupBy)
63 | * @method Query setValue($column, $value)
64 | * @method Query values(array $values)
65 | * @method Query having($having)
66 | * @method Query andHaving($having)
67 | * @method Query orHaving($having)
68 | * @method Query orderBy($sort, $order = null)
69 | * @method Query addOrderBy($sort, $order = null)
70 | * @method mixed getQueryPart($queryPartName)
71 | * @method array getQueryParts()
72 | * @method Query resetQueryParts($queryPartNames = null)
73 | * @method Query resetQueryPart($queryPartName)
74 | * @method string __toString()
75 | * @method string createNamedParameter($value, $type = \PDO::PARAM_STR, $placeHolder = null)
76 | * @method string createPositionalParameter($value, $type = \PDO::PARAM_STR)
77 | * @method void __clone()
78 | *
79 | * @package Blast\Db\Orm
80 | */
81 | class Query implements ConnectionAwareInterface, EmitterAwareInterface,
82 | EntityAwareInterface, ProviderFactoryInterface, QueryInterface
83 | {
84 | use ConnectionAwareTrait;
85 | use EmitterAwareTrait;
86 | use EntityAwareTrait;
87 | use ProviderFactoryTrait;
88 |
89 | /**
90 | * @var QueryBuilder
91 | */
92 | private $builder;
93 |
94 | /**
95 | * Statement constructor.
96 | *
97 | * @param \Doctrine\DBAL\Connection $connection
98 | * @param array|stdClass|\ArrayObject|object|string $entity
99 | */
100 | public function __construct($connection = null, $entity = null)
101 | {
102 | $this->setConnection($connection);
103 | $this->setEntity($entity);
104 | }
105 |
106 | /**
107 | * Fetch data for entity
108 | *
109 | * @param string $option
110 | * @return array|\SplStack|\ArrayObject|bool
111 | * @throws \Doctrine\DBAL\DBALException
112 | */
113 | public function execute($option = HydratorInterface::HYDRATE_AUTO)
114 | {
115 | //execute before events and proceed with builder from event
116 | $provider = $this->createProvider($this->getEntity());
117 | $event = $this->beforeExecute($provider->getEntity());
118 |
119 | if ($event->isCanceled()) {
120 | return false;
121 | }
122 |
123 | $builder = $event->getBuilder();
124 |
125 | //convert entity to adapter again
126 | $provider = $this->createProvider($builder->getEntity());
127 |
128 | $connection = $this->getConnection();
129 | $isSelect = $builder->getType() === QueryBuilder::SELECT;
130 |
131 | $sql = $this->getSQL();
132 |
133 | $statement = $isSelect ?
134 | //execute query and receive a statement
135 | $connection->executeQuery($sql, $this->getParameters(), $this->getParameterTypes()) :
136 |
137 | //execute query and receive a count of affected rows
138 | $connection->executeUpdate($sql, $this->getParameters(), $this->getParameterTypes());
139 |
140 | //execute after events and proceed with result from event
141 | $event = $this->afterExecute(
142 | $isSelect ?
143 | $statement->fetchAll() :
144 | $statement,
145 | $provider->getEntity(), $builder);
146 |
147 | if ($event->isCanceled()) {
148 | return false;
149 | }
150 |
151 | $result = $this->convertTypesToPHPValues($provider, $event->getResult());
152 |
153 | $data = (new EntityHydrator($provider))->hydrate($result, $option);
154 | gc_collect_cycles();
155 |
156 | return $data;
157 | }
158 |
159 | /**
160 | * Emit events before query handling and if entity is able to emit events execute entity events
161 | *
162 | * @param $entity
163 | * @return QueryBuilderEvent
164 | */
165 | private function beforeExecute($entity)
166 | {
167 | $builder = $this;
168 | $event = $this->getEmitter()->emit(new QueryBuilderEvent('build.' . $this->getTypeName(), $builder));
169 |
170 | if ($entity instanceof EmitterAwareInterface) {
171 | $event = $entity->getEmitter()->emit($event);
172 | }
173 |
174 | return $event;
175 | }
176 |
177 | /**
178 | * Emit events after query handling and if entity is able to emit events execute entity events
179 | *
180 | * @param mixed $result Raw result
181 | * @param mixed $entity Entity which contains the events
182 | * @param Query $builder
183 | * @return QueryResultEvent
184 | */
185 | private function afterExecute($result, $entity, $builder)
186 | {
187 |
188 | $event = $this->getEmitter()->emit(new QueryResultEvent('result.' . $builder->getTypeName(), $result), $builder);
189 |
190 | if ($entity instanceof EmitterAwareInterface) {
191 | $event = $entity->getEmitter()->emit($event, $builder);
192 | }
193 |
194 | return $event;
195 | }
196 |
197 | /**
198 | * Specifies an item that is to be returned in the query result.
199 | * Replaces any previously specified selections, if any.
200 | *
201 | *
202 | * $qb = $conn->createQueryBuilder()
203 | * ->select('u.id', 'p.id')
204 | * ->from('users', 'u')
205 | * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
206 | *
207 | *
208 | * @param array $select The selection expressions.
209 | *
210 | * @return $this Query instance
211 | */
212 | public function select(array $select = [])
213 | {
214 | if (count($select) < 1) {
215 | $select = ['*'];
216 | }
217 |
218 | return $this->__call(__FUNCTION__, [$select]);
219 | }
220 |
221 | /**
222 | * Magic call of \Doctrine\DBAL\Query\QueryBuilder methods
223 | *
224 | * @param string|callable $name
225 | * @param array $arguments
226 | * @return mixed
227 | */
228 | public function __call($name, array $arguments = [])
229 | {
230 | $result = call_user_func_array([$this->getBuilder(), $name], $arguments);
231 |
232 | return $result instanceof QueryBuilder ? $this : $result;
233 | }
234 |
235 | /**
236 | * @return \Doctrine\DBAL\Query\QueryBuilder
237 | */
238 | public function getBuilder()
239 | {
240 | if (null === $this->builder) {
241 | $this->builder = $this->getConnection()->createQueryBuilder();
242 | }
243 |
244 | return $this->builder;
245 | }
246 |
247 | /**
248 | * @param \Doctrine\DBAL\Query\QueryBuilder $builder
249 | *
250 | * @return $this
251 | */
252 | public function setBuilder(QueryBuilder $builder)
253 | {
254 | $this->builder = $builder;
255 |
256 | return $this;
257 | }
258 |
259 | /**
260 | * Get query type name
261 | * @return string
262 | * @throws \Exception
263 | */
264 | public function getTypeName()
265 | {
266 | switch ($this->getType()) {
267 | case QueryBuilder::SELECT:
268 | return 'select';
269 | case QueryBuilder::INSERT:
270 | return 'insert';
271 | case QueryBuilder::UPDATE:
272 | return 'update';
273 | case QueryBuilder::DELETE:
274 | return 'delete';
275 | // @codeCoverageIgnoreStart
276 | default:
277 | //this could only happen if query will be extended and a custom getType is return invalid type
278 | throw new \Exception('Unknown query type ' . $this->getType());
279 | }
280 | // @codeCoverageIgnoreEnd
281 | }
282 |
283 | /**
284 | * @param $provider
285 | * @param $result
286 | * @return mixed
287 | * @throws \Doctrine\DBAL\DBALException
288 | */
289 | private function convertTypesToPHPValues(ProviderInterface $provider, $result)
290 | {
291 | if(!is_array($result)){
292 | return $result;
293 | }
294 | $fields = $provider->getDefinition()->getFields();
295 |
296 | foreach ($result as $index => $items) {
297 | foreach ($items as $key => $value) {
298 | $defaultType = Type::getType(is_numeric($value) ? Type::INTEGER : Type::STRING);
299 | $type = array_key_exists($key, $fields) ? $fields[$key]->getType() : $defaultType;
300 | $result[$index][$key] = $type->convertToPHPValue(
301 | $value, $this->getConnection()->getDatabasePlatform()
302 | );
303 | }
304 | }
305 |
306 | return $result;
307 | }
308 |
309 | /**
310 | * Add a value for column on update or insert statement
311 | *
312 | * @param $column
313 | * @param $value
314 | *
315 | * @return $this
316 | */
317 | public function addColumnValue($column, $value){
318 | switch($this->getType()){
319 | case QueryBuilder::INSERT:
320 | $this->setValue($column, $value);
321 | break;
322 | case QueryBuilder::UPDATE:
323 | $this->set($column, $value);
324 | break;
325 | }
326 |
327 | return $this;
328 | }
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blast ORM
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Software License][ico-license]](LICENSE.md)
5 | [![Build Status][ico-travis]][link-travis]
6 | [![Total Downloads][ico-downloads]][link-downloads]
7 | [![Coverage Status][ico-coveralls]][link-coveralls]
8 |
9 | Framework agnostic data access and persistence based on Doctrine 2 DBAL.
10 |
11 | ## Features
12 |
13 | - Data and relation mapper _since 0.1_
14 | - Decoupled entities as POPO's (Plain-old PHP objects) _since 0.3_
15 | - Auto-Suggesting entity definition as well as configure custom definition _since 0.5_
16 | - Data hydration to entity and vice versa _since 0.5_
17 | - Repository contracted to a single entity class _since 0.5_
18 | - Integration of fields from definition _since 0.5.2_
19 | - Field type aware converting _since 0.5.2_
20 | - Integration of indexes from definition _since 0.6.4_
21 | - entity independent and connection dependent table prefixes _since 0.6.4_
22 | - entity reflection and meta data caching _since 0.6.4_
23 |
24 | ## Upcoming features
25 |
26 | - Unit of Work - Entity-aware transactions
27 | - Identity map - Reduce load by storing entity by primary key
28 |
29 | ## Install
30 |
31 | ### Using Composer
32 |
33 | Blast ORM is available on [Packagist](https://packagist.org/packages/blast/orm) and can be installed using [Composer](https://getcomposer.org/). This can be done by running the following command or by updating your `composer.json` file.
34 |
35 | ```bash
36 | composer require blast/orm
37 | ```
38 |
39 | composer.json
40 |
41 | ```json
42 | {
43 | "require": {
44 | "blast/orm": "~1.0"
45 | }
46 | }
47 | ```
48 |
49 | Be sure to also include your Composer autoload file in your project:
50 |
51 | ```php
52 | = 5.5.9
66 | * PHP 5.6
67 | * PHP 7.0
68 | * HHVM
69 |
70 | ## Example
71 |
72 | An example can be found in this [blog post](http://bit.ly/php-orm).
73 |
74 | ## Concept
75 |
76 | ### Entities
77 |
78 | An entity object is an in-memory representations of a database entity. Entity object are plain objects (aka POPO).
79 | It is recommended to use accessors (getters) and mutators (setters) for properties on plain objects.
80 |
81 | #### Provider
82 |
83 | The provider is a link between independent data entity and data access. The provider is also able to hydrate data to
84 | entity object and extract data from entity object.
85 |
86 | #### Definition
87 |
88 | Definition managing entity meta and schema specific configuration. Definition could be passed to mapper or provider,
89 | instead of an entity.
90 |
91 | ### Mappers
92 |
93 | Each entity does have it's own mapper. A mapper is determined by the entity provider. Mappers mediate between dbal
94 | and entity and provide convenient CRUD (Create, Read, Update, Delete). In addition to CRUD, the mapper is also delivering
95 | convenient methods to to work with relations.
96 |
97 | ### Query
98 |
99 | The query acts as accessor to the persistence layer. The query class is hydrating data on execution and transforms the
100 | result into a single entity class or `\ArrayObject` as fallback, a collection of entity classes as `\SplStack` or as a
101 | raw data array. Furthermore the query is able to receive hydration options to control the result. Create, delete and
102 | update are always returning a numeric value!
103 |
104 | ### Repository
105 |
106 | The repository is mediating between persistence layer and abstract from persistence or data access through mapper or query.
107 | Blast orm is just delivering a `Blast\Orm\RepositoryInterface` for completion!
108 |
109 | ## Usage
110 |
111 | ### Connections
112 |
113 | Blast ORM is managing all connections with `\Blast\Orm\ConnectionManager`. You are able to create connections directly
114 | or add connections to manager cache and access them later on.
115 |
116 | #### Direct access
117 |
118 | Create a new connection
119 |
120 | ```php
121 | add('mysql://root:root@localhost/defaultdb?charset=UTF-8', 'myconnectionname');
152 | ```
153 |
154 | Get connection, default connection name is always `default`
155 |
156 | ```php
157 | get();
161 |
162 | //get connection by name
163 | $anotherConnection = $connections->get('another');
164 | ```
165 |
166 | Swap default connection with another connection.
167 |
168 | ```php
169 | setDefaultConnection('another');
171 |
172 | ```
173 |
174 | ### Query
175 |
176 | The query object is is providing high level API methods of [doctrine 2 query builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html#security-safely-preventing-sql-injection).
177 |
178 | Create query from connection
179 |
180 | ```php
181 | createQuery();
184 | ```
185 |
186 | with entity
187 |
188 | ```php
189 | createQuery(Post::class);
192 | ```
193 |
194 | with definition
195 |
196 | ```php
197 | createQuery($definition);
200 |
201 | ```
202 |
203 | Create a new query instance, the query is automatically determining current active connection from connection manager.
204 |
205 | ```php
206 | setConnection(ConnectionManager::create('mysql://root:root@localhost/acme'));
242 |
243 | ```
244 |
245 | Custom query builder
246 |
247 | ```php
248 | setBuilder($connection->createQueryBuilder());
251 |
252 | ```
253 |
254 | #### select
255 |
256 | Get all posts as collection `\SplStack` containing post as `\ArrayObject`
257 |
258 | ```php
259 | select()->from('post', 'p')->execute();
262 |
263 | //get data from result
264 | $title = $result['title'];
265 | $content = $result['content'];
266 | ```
267 |
268 | Get post by id as `\ArrayObject`
269 |
270 | ```php
271 | select()
275 | ->from('post', 'p')
276 | ->where('id = :id')
277 | ->setParameter('id', $id)
278 | ->execute();
279 |
280 | //loop results and get data
281 | foreach($results as $result){
282 | $title = $result['title'];
283 | $content = $result['content'];
284 | }
285 |
286 | ```
287 |
288 | #### create
289 |
290 | Create a new entry and get number of affected rows
291 |
292 | ```php
293 | insert('post')
296 | ->setValue('title', 'New Blog post')
297 | ->setValue('content', 'some blog content')
298 | ->execute();
299 | ```
300 |
301 | #### update
302 |
303 | Update an entry and get number of affected rows
304 |
305 | ```php
306 | update('post')
309 | ->set('title', 'New Blog post')
310 | ->where('id = :id')
311 | ->setParameter('id', 1)
312 | ->execute();
313 | ```
314 |
315 | #### delete
316 |
317 | Delete entries and get number of affected rows
318 |
319 | ```php
320 | delete('post')
323 | ->where('id = :id')
324 | ->setParameter('id', 1)
325 | ->execute();
326 | ```
327 |
328 | #### Advanced execution for select
329 |
330 | Execute query and get result as entity
331 |
332 | ```php
333 | execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_ENTITY);
336 | ```
337 |
338 | Execute query and get result as collection
339 |
340 | ```php
341 | execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_COLLECTION);
344 | ```
345 |
346 | Execute query and get raw result as array
347 |
348 | ```php
349 | execute(\Blast\Orm\Hydrator\HydratorInterface::HYDRATE_RAW);
352 | ```
353 |
354 | #### Events
355 |
356 | Query is able to execute events for each statement type.
357 |
358 | - select
359 | - insert
360 | - update
361 | - delete
362 |
363 | ##### build.{type}
364 |
365 | Fire this event before query executes statement.
366 |
367 | ```php
368 | getEmitter()->addListener('build.select', function (QueryBuilderEvent $event) {
373 | $event->getBuilder()->setEntity(Post::class);
374 | });
375 |
376 | $result = $query->select()->from('post')->where('id = 1')->execute();
377 |
378 | ```
379 |
380 | ##### result.{type}
381 |
382 | Fire this event after query executes statement and receives result.
383 |
384 | ```php
385 | getEmitter()->addListener('result.select', function (QueryResultEvent $event, Query $builder) {
391 | $result = $event->getResult();
392 |
393 | foreach ($result as $key => $value) {
394 | $result[$key]['contentSize'] = strlen($value['content']);
395 | }
396 |
397 | $event->setResult($result);
398 | });
399 |
400 | $result = $query->select()->from('post')->where('id = 1')->execute();
401 |
402 | ```
403 |
404 | ###### Canceling query execution
405 |
406 | Use `setCancel()` method from given event to cancel a query execution.
407 |
408 | On build a query statement
409 |
410 | ```php
411 | getEmitter()->addListener('build.select', function (QueryBuilderEvent $event) {
416 | $event->setCanceled(true);
417 | });
418 | ```
419 |
420 | On result a query statement
421 |
422 | ```php
423 | getEmitter()->addListener('result.select', function (QueryResultEvent $event) {
428 | $event->setCanceled(true);
429 | });
430 | ```
431 |
432 | ### Entities
433 |
434 | Entity classes are independent of Blast ORM. Entity fields are translated to underscore for database field mapping.
435 | Database fields are translated to camelcase for entity field mapping.
436 |
437 | ```php
438 | setConfiguration([
460 | 'tableName' => 'user_role'
461 | ]);
462 |
463 | //from mapper
464 | $mapper = new Mapper($definition);
465 |
466 | //from query
467 | $query = new Query($connection, $definition);
468 |
469 | ```
470 |
471 | A list of possible configuration
472 |
473 | ```php
474 | \ArrayObject::class,
478 | 'entityCollection' => \SplStack::class,
479 | 'events' => [],
480 | 'fields' => [],
481 | 'indexes' => [],
482 | 'primaryKeyName' => ProviderInterface::DEFAULT_PRIMARY_KEY_NAME,
483 | 'tableName' => '',
484 | 'mapper' => Mapper::class,
485 | 'relations' => []
486 | ];
487 | $definition->setConfiguration($configuration);
488 | ```
489 |
490 | A definition could also be declared on an entity as FQCN by property
491 |
492 | ```php
493 | hasOne($entity, 'otherTable')
657 | ];
658 | }
659 | }
660 |
661 | ```
662 |
663 | ##### Access definition from provider
664 |
665 | Adapters grant access to data and definition, even if your entity class does not have definitions at all.
666 |
667 | ```php
668 | getTableName();
683 |
684 | ```
685 |
686 | Get primary key name
687 |
688 | ```php
689 | getPrimaryKeyName();
691 |
692 | ```
693 |
694 | Get entities mapper
695 |
696 | ```php
697 | getMapper();
699 |
700 | ```
701 |
702 | Get entities relation
703 |
704 | ```php
705 | getRelations();
707 |
708 | ```
709 |
710 | Hydrate data as array to entity
711 |
712 | ```php
713 | hydrate(['title' => 'Hello World']);
715 |
716 | ```
717 |
718 | Hydrate data as entity to array
719 |
720 | ```php
721 | extract();
723 |
724 | ```
725 |
726 | ### Mapper
727 |
728 | The mapper prepares queries for data persistence and access of a provided entity class. All methods always return a query
729 | instance and need to execute manually. It is also possible to add event listeners for query
730 |
731 | #### Create a new mapper for entity
732 |
733 | Get entity specific mapper from connection
734 |
735 | ```php
736 | getMapper($post);
739 |
740 | ```
741 |
742 | Get entity specific mapper from provider
743 |
744 | ```php
745 | getDefinition()->getMapper();
751 |
752 | ```
753 |
754 | #### find
755 |
756 | Fetch one entry by primary key
757 |
758 | ```php
759 | find(1)->execute();
762 |
763 | ```
764 |
765 | #### select
766 |
767 | Custom select query
768 |
769 | ```php
770 | select()
773 | ->where('title = "Hello world"')
774 | ->setMaxResults(1)
775 | ->execute(EntityHydratorInterface::RESULT_ENTITY);
776 | ```
777 |
778 | #### delete
779 |
780 | `delete` expects an primary key or an array of primary keys.
781 |
782 | Delete onw entry
783 |
784 | ```php
785 | delete(1);
788 | ```
789 |
790 | Delete many entries
791 |
792 | ```php
793 | delete([1, 2]);
796 | ```
797 |
798 | ### Relations
799 |
800 | Relations provide access to related, parent and child entity from another entity.
801 |
802 | #### Passing relations
803 |
804 | Pass relations as `array` by computed static relation method in entity class. Relations are automatically mapped to entity.
805 |
806 | ```php
807 | comments;
817 | }
818 |
819 | public static function relation(Post $entity, Mapper $mapper){
820 | return [
821 | $mapper->hasMany($entity, Comments::class)
822 | ];
823 | }
824 | }
825 |
826 | $post = $mapper->find(1);
827 | $comments = $post->getComments()->execute();
828 |
829 | ```
830 |
831 | You could also extend the relation query with `RelationInterface::getQuery`.
832 |
833 | #### HasOne (one-to-one)
834 |
835 | One entity is associated with one related entity by a field which associates with primary key in current entity.
836 |
837 | - `$entity` - Current entity instance
838 | - `$foreignEntity` - Entity class name, instance or table name
839 | - `$foreignKey` - Field name on related entity. `null` by default. Empty foreign key is determined by current entity table name and primary key name as follows: `{tableName}_{primaryKeyName}`, e.g `user_id`
840 |
841 | ##### Example
842 |
843 | One user has one address.
844 |
845 | ```php
846 | hasOne($user, Address::class, 'user_id');
849 |
850 | ```
851 |
852 | #### HasMany (one-to-many)
853 |
854 | One entity is associated with many related entities by a field which associates with primary key in current entity.
855 |
856 | - `$entity` - Current entity instance
857 | - `$foreignEntity` - Entity class name, instance or table name
858 | - `$foreignKey` - Field name on a related entity. `null` by default. Empty foreign key is determined by current entity table name and primary key name as follows: `{tableName}_{primaryKeyName}`, e.g `post_id`
859 |
860 | ##### Example
861 |
862 | One post has many comments
863 |
864 | ```php
865 | hasMany($post, Comments::class, 'post_id');
868 |
869 | ```
870 |
871 | #### BelongsTo (one-to-one or one-to-many)
872 |
873 | BelongsTo is the inverse of a HasOne or a HasMany relation.
874 |
875 | One entity is associated with one related entity by a field which associates with primary key in related entity.
876 |
877 | - `$entity` - Current entity instance
878 | - `$foreignEntity` - Entity class name, instance or table name
879 | - `$localKey` - Field name on current entity. `null` by default. Empty local key is determined by related entity table name and primary key name as follows: `{tableName}_{primaryKeyName}`, e.g `post_id`
880 |
881 | ##### Example
882 |
883 | One post has one or many comments
884 |
885 | ```php
886 | belongsTo($comment, Post::class, 'post_id');
889 |
890 | ```
891 |
892 | #### ManyToMany (many-to-many)
893 |
894 | Many entities of type _A_ are associated with many related entities of type _B_ by a junction table. The junction table
895 | stores associations from entities of type _A_ to entities of type _B_.
896 |
897 | - `$entity`: Current entity instance
898 | - `$foreignEntity`: Entity class name, instance or table name
899 | - `$foreignKey` - Field name on a related entity. `null` by default. Empty foreign key is determined by current primary key name.
900 | - `$localKey`: Field name on current entity. `null` by default. Empty foreign key is determined by related entity primary key name.
901 | - `$junction`: Junction table name. `null` by default. Empty table name is determined by entity table name and foreign entity table name as follows: `{tableName}_{foreignTableName}`, e.g `post_comment`.
902 | - `$junctionLocalKey`: Field name on a related entity. `null` by default. Empty junction local key is determined by current entity table name and primary key name as follows: `{tableName}_{primaryKeyName}`, e.g `post_id`.
903 | - `$junctionForeignKey`: Field name on current entity. `null` by default. Empty junction foreign key is determined by related entity table name and primary key name as follows: `{tableName}_{primaryKeyName}`, e.g `comment_id`.
904 |
905 | ##### Example
906 |
907 | One user has many roles, and one role has many users. Users primary key name is `id`, Roles primary key name is `pk`
908 | (Primary key short name). The junction table `user_role` contains `user_id` and `role_id` columns.
909 |
910 | ```php
911 | manyToMany($user, Role::class, 'pk', 'id', 'user_role', 'user_id', 'role_id');
914 |
915 | ```
916 |
917 | ### Repository
918 |
919 | The repository abstracts methods for data persistence and access. All methods execute their queries directly.
920 |
921 | Blast ORM provides a repository interface `\Blast\Orm\RepositoryInterface`.
922 |
923 | A repository knows it's entity. Therefore we need to pass the entity as class name or instance
924 |
925 | Create from interface
926 |
927 | ```php
928 | createMapper($this->getEntity())->select()->execute(HydratorInterface::HYDRATE_COLLECTION);
955 | }
956 |
957 | /**
958 | * Find entity by primary key
959 | *
960 | * @param mixed $primaryKey
961 | * @return \ArrayObject|\stdClass|object
962 | */
963 | public function find($primaryKey)
964 | {
965 | return $this->createMapper($this->getEntity())->find($primaryKey)->execute(HydratorInterface::HYDRATE_ENTITY);
966 | }
967 |
968 | /**
969 | * Save new or existing entity data
970 | *
971 | * @param object|array $data
972 | * @return int|bool
973 | */
974 | public function save($data)
975 | {
976 | return $this->createMapper($data)->save($data)->execute();
977 | }
978 |
979 | }
980 |
981 | ```
982 |
983 | Create repository by abstract
984 |
985 | ```php
986 | setEntity(Post::class);
999 | }
1000 | }
1001 | ```
1002 |
1003 | Create repository instance
1004 |
1005 | ```php
1006 | find(1);
1020 |
1021 | ```
1022 |
1023 | #### all
1024 |
1025 | Fetch all entries and return as collection `Blast\Orm\Data\DataObject`
1026 |
1027 | ```php
1028 | all();
1031 |
1032 | foreach($posts as $post){
1033 |
1034 | //do something
1035 |
1036 | }
1037 |
1038 | ```
1039 |
1040 | #### save
1041 |
1042 | Save is determining if the entity is new and executes `Blast\Orm\Mapper::update` or if it is new `Blast\Orm\Mapper::create`.
1043 |
1044 | ```php
1045 | save($post);
1055 | ```
1056 |
1057 | ## Further development
1058 |
1059 | Please visit our [milestones](https://github.com/phpthinktank/blast-orm/milestones)
1060 |
1061 | ## Change log
1062 |
1063 | Please see [CHANGELOG](ChangeLog-0.1.md) for more information what has changed recently.
1064 |
1065 | ## Testing
1066 |
1067 | ``` bash
1068 | $ composer test
1069 | ```
1070 |
1071 | ## Contributing
1072 |
1073 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
1074 |
1075 | ## Security
1076 |
1077 | If you discover any security related issues, please email instead of using the issue tracker.
1078 |
1079 | ## Credits
1080 |
1081 | - [Marco Bunge][link-author]
1082 | - [All contributors][link-contributors]
1083 |
1084 | ## License
1085 |
1086 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
1087 |
1088 | [ico-version]: https://img.shields.io/packagist/v/blast/orm.svg?style=flat-square
1089 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
1090 | [ico-travis]: https://img.shields.io/travis/phpthinktank/blast-orm/master.svg?style=flat-square
1091 | [ico-downloads]: https://img.shields.io/packagist/dt/blast/orm.svg?style=flat-square
1092 | [ico-coveralls]: https://img.shields.io/coveralls/phpthinktank/blast-orm/master.svg?style=flat-square)](https://coveralls.io/github/phpthinktank/blast-orm?branch=master
1093 |
1094 | [link-packagist]: https://packagist.org/packages/blast/orm
1095 | [link-travis]: https://travis-ci.org/phpthinktank/blast-orm
1096 | [link-downloads]: https://packagist.org/packages/blast/orm
1097 | [link-author]: https://github.com/mbunge
1098 | [link-contributors]: ../../contributors
1099 | [link-coveralls]: https://coveralls.io/github/phpthinktank/blast-orm
1100 |
--------------------------------------------------------------------------------