├── .bootstrap.atoum.php ├── .gitignore ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── README.md ├── VERSION ├── composer.json ├── composer.lock ├── documentation └── model_manager.rst └── sources ├── lib ├── Converter │ └── PgEntity.php ├── Exception │ ├── GeneratorException.php │ ├── ModelException.php │ └── ModelLayerException.php ├── Generator │ ├── BaseGenerator.php │ ├── EntityGenerator.php │ ├── ModelGenerator.php │ └── StructureGenerator.php ├── Model │ ├── CollectionIterator.php │ ├── FlexibleEntity.php │ ├── FlexibleEntity │ │ ├── FlexibleContainer.php │ │ ├── FlexibleEntityInterface.php │ │ └── StatefulEntityTrait.php │ ├── HydrationPlan.php │ ├── IdentityMapper.php │ ├── Model.php │ ├── ModelPooler.php │ ├── ModelTrait │ │ ├── BaseTrait.php │ │ ├── ReadQueries.php │ │ └── WriteQueries.php │ ├── Projection.php │ └── RowStructure.php ├── ModelLayer │ ├── ModelLayer.php │ └── ModelLayerPooler.php ├── Session.php ├── SessionBuilder.php └── Tester │ └── ModelSessionAtoum.php └── tests ├── Fixture ├── ComplexFixture.php ├── ComplexFixtureModel.php ├── ComplexFixtureStructure.php ├── ComplexNumber.php ├── ComplexNumberStructure.php ├── ModelSchemaClient.php ├── ReadFixtureModel.php ├── SimpleFixture.php ├── SimpleFixtureModel.php ├── SimpleFixtureStructure.php ├── SimpleModelLayer.php ├── WeirdFixture.php ├── WeirdFixtureModel.php ├── WithoutPKFixture.php ├── WithoutPKFixtureModel.php ├── WithoutPKFixtureStructure.php └── WriteFixtureModel.php ├── Unit ├── BaseTest.php ├── Converter │ └── PgEntity.php ├── Model │ ├── CollectionIterator.php │ ├── CollectionQuery.php │ ├── FlexibleEntity.php │ ├── FlexibleEntity │ │ └── FlexibleContainer.php │ ├── IdentityMapper.php │ ├── Model.php │ ├── ModelPooler.php │ ├── Projection.php │ └── RowStructure.php └── ModelLayer │ └── ModelLayer.php ├── config.php.dist └── config.travis.php /.bootstrap.atoum.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the “Software”), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 7 | to whom the Software is furnished to do so, subject to the following conditions: 8 | The above copyright notice and this permission notice shall be included in all copies or 9 | substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 12 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 13 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN- 14 | FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 15 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 16 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 17 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pomm ModelManager 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/pomm-project/model-manager/v/stable)](https://packagist.org/packages/pomm-project/model-manager) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pomm-project/ModelManager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pomm-project/ModelManager/?branch=master) [![Build Status](https://travis-ci.org/pomm-project/ModelManager.svg)](https://travis-ci.org/pomm-project/ModelManager) [![Monthly Downloads](https://poser.pugx.org/pomm-project/model-manager/d/monthly.png)](https://packagist.org/packages/pomm-project/model-manager) [![License](https://poser.pugx.org/pomm-project/model-manager/license.svg)](https://packagist.org/packages/pomm-project/model-manager) 4 | 5 | 6 | ModelManager is a [Pomm project](http://www.pomm-project.org) package. It makes developers able to manage entities upon the database through model classes. **It is not an ORM**, it grants developers with the ability to perform native queries using all of Postgres’SQL and use almost all its types. This makes model layer to meet with performances while staying lean. 7 | 8 | This package will provide: 9 | 10 | * Model classes with all common built-in queries (CRUD but also, `count` and `exists`). 11 | * Flexible entities 12 | * Embedded entities converter 13 | * Model Layer to group model computations in transactions. 14 | 15 | The model layer also proposes methods to leverage Postgres nice transaction settings (constraint deferring, isolation levels, read / write access modes etc.). 16 | 17 | ## Installation 18 | 19 | Pomm components are available on [packagist](https://packagist.org/packages/pomm-project/) using [composer](https://packagist.org/). To install and use Pomm's model manager, add a require line to `"pomm-project/model-manager"` in your `composer.json` file. It is advised to install the [CLI package](https://github.com/pomm-project/Cli) as well. 20 | 21 | In order to load the model manager's poolers at startup, it is possible to use the provided `SessionBuilder` in Pomm's configuration: 22 | 23 | ```php 24 | $pomm = new Pomm([ 25 | 'project_name' => [ 26 | 'dsn' => …, 27 | 'class:session_builder' => '\PommProject\ModelManager\SessionBuilder', 28 | ], 29 | … 30 | ]); 31 | ``` 32 | 33 | It is better to provide dedicated session builders with your project. 34 | 35 | ## Documentation 36 | 37 | The model manager’s documentation is available [either online](https://github.com/pomm-project/ModelManager/blob/master/documentation/model_manager.rst) or directly in the `documentation` folder. 38 | 39 | ## Tests 40 | 41 | This package uses Atoum as unit test framework. The tests are located in `sources/tests`. This package also provides a `ModelSessionAtoum` class so the test classes can directly get sessions with the `model` and `model layer` poolers loaded. 42 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.3-dev 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomm-project/model-manager", 3 | "type": "library", 4 | "description": "PHP Object Model Manager for Postgresql", 5 | "keywords": ["orm", "postgresql", "database", "pomm"], 6 | "homepage": "http://www.pomm-project.org", 7 | "license": "MIT", 8 | "authors": [ { "name": "Grégoire HUBERT", "email": "hubert.greg@gmail.com", "homepage": "http://www.pomm-project.org" } ], 9 | "require": { 10 | "php": ">=5.4.4", 11 | "ext-pgsql": "*", 12 | "psr/log": "~1.0", 13 | "pomm-project/foundation": "~2.0" 14 | }, 15 | "require-dev": { 16 | "atoum/atoum" : "dev-master" 17 | }, 18 | "suggest": { 19 | "pomm-project/cli": "dev-master" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "PommProject\\ModelManager\\": "sources/lib" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "PommProject\\ModelManager\\Test\\": "sources/tests" 29 | } 30 | }, 31 | "minimum-stability": "stable" 32 | } 33 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "5fdb0da3f01bb06094652b118f3d4d77", 8 | "content-hash": "9d427cdd6a8e6ed0317227dee3ad2480", 9 | "packages": [ 10 | { 11 | "name": "pomm-project/foundation", 12 | "version": "dev-master", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/pomm-project/Foundation.git", 16 | "reference": "ab114b4f1dc8cb131fbf8ab93823b34620880376" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/pomm-project/Foundation/zipball/ab114b4f1dc8cb131fbf8ab93823b34620880376", 21 | "reference": "ab114b4f1dc8cb131fbf8ab93823b34620880376", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-pgsql": "*", 26 | "php": ">=5.4.4", 27 | "psr/log": "~1.0" 28 | }, 29 | "require-dev": { 30 | "atoum/atoum": "dev-master" 31 | }, 32 | "type": "library", 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "2.0-dev" 36 | } 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "PommProject\\Foundation\\": "sources/lib" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Grégoire HUBERT", 50 | "email": "hubert.greg@gmail.com", 51 | "homepage": "http://www.coolkeums.org" 52 | } 53 | ], 54 | "description": "Pomm connection manager for Postgresql", 55 | "homepage": "http://www.pomm-project.org", 56 | "keywords": [ 57 | "database", 58 | "framework", 59 | "pomm", 60 | "postgresql" 61 | ], 62 | "time": "2015-10-06 14:32:14" 63 | }, 64 | { 65 | "name": "psr/log", 66 | "version": "1.0.0", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/php-fig/log.git", 70 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 75 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", 76 | "shasum": "" 77 | }, 78 | "type": "library", 79 | "autoload": { 80 | "psr-0": { 81 | "Psr\\Log\\": "" 82 | } 83 | }, 84 | "notification-url": "https://packagist.org/downloads/", 85 | "license": [ 86 | "MIT" 87 | ], 88 | "authors": [ 89 | { 90 | "name": "PHP-FIG", 91 | "homepage": "http://www.php-fig.org/" 92 | } 93 | ], 94 | "description": "Common interface for logging libraries", 95 | "keywords": [ 96 | "log", 97 | "psr", 98 | "psr-3" 99 | ], 100 | "time": "2012-12-21 11:40:51" 101 | } 102 | ], 103 | "packages-dev": [ 104 | { 105 | "name": "atoum/atoum", 106 | "version": "dev-master", 107 | "source": { 108 | "type": "git", 109 | "url": "https://github.com/atoum/atoum.git", 110 | "reference": "e6aab9a885c3133419a48df858bd4481a3505552" 111 | }, 112 | "dist": { 113 | "type": "zip", 114 | "url": "https://api.github.com/repos/atoum/atoum/zipball/e6aab9a885c3133419a48df858bd4481a3505552", 115 | "reference": "e6aab9a885c3133419a48df858bd4481a3505552", 116 | "shasum": "" 117 | }, 118 | "require": { 119 | "ext-hash": "*", 120 | "ext-json": "*", 121 | "ext-session": "*", 122 | "ext-tokenizer": "*", 123 | "ext-xml": "*", 124 | "php": ">=5.3.3" 125 | }, 126 | "replace": { 127 | "mageekguy/atoum": "*" 128 | }, 129 | "suggest": { 130 | "atoum/stubs": "Provides IDE support (like autocompletion) for atoum", 131 | "ext-mbstring": "Provides support for UTF-8 strings" 132 | }, 133 | "bin": [ 134 | "bin/atoum" 135 | ], 136 | "type": "library", 137 | "extra": { 138 | "branch-alias": { 139 | "dev-master": "2.x-dev" 140 | } 141 | }, 142 | "autoload": { 143 | "classmap": [ 144 | "classes/" 145 | ] 146 | }, 147 | "notification-url": "https://packagist.org/downloads/", 148 | "license": [ 149 | "BSD" 150 | ], 151 | "authors": [ 152 | { 153 | "name": "Frédéric Hardy", 154 | "email": "frederic.hardy@atoum.org", 155 | "homepage": "http://blog.mageekbox.net" 156 | }, 157 | { 158 | "name": "François Dussert", 159 | "email": "francois.dussert@atoum.org" 160 | }, 161 | { 162 | "name": "Gérald Croes", 163 | "email": "gerald.croes@atoum.org" 164 | }, 165 | { 166 | "name": "Julien Bianchi", 167 | "email": "julien.bianchi@atoum.org" 168 | }, 169 | { 170 | "name": "Ludovic Fleury", 171 | "email": "ludovic.fleury@atoum.org" 172 | } 173 | ], 174 | "description": "Simple modern and intuitive unit testing framework for PHP 5.3+", 175 | "homepage": "http://www.atoum.org", 176 | "keywords": [ 177 | "TDD", 178 | "atoum", 179 | "test", 180 | "unit testing" 181 | ], 182 | "time": "2015-10-23 07:26:05" 183 | } 184 | ], 185 | "aliases": [], 186 | "minimum-stability": "stable", 187 | "stability-flags": { 188 | "pomm-project/foundation": 20, 189 | "atoum/atoum": 20 190 | }, 191 | "prefer-stable": false, 192 | "prefer-lowest": false, 193 | "platform": { 194 | "php": ">=5.4.4", 195 | "ext-pgsql": "*" 196 | }, 197 | "platform-dev": [] 198 | } 199 | -------------------------------------------------------------------------------- /sources/lib/Converter/PgEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Converter; 11 | 12 | use PommProject\Foundation\Converter\ConverterInterface; 13 | use PommProject\Foundation\Exception\ConverterException; 14 | use PommProject\Foundation\Session\Session; 15 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 16 | use PommProject\ModelManager\Model\HydrationPlan; 17 | use PommProject\ModelManager\Model\IdentityMapper; 18 | use PommProject\ModelManager\Model\Projection; 19 | use PommProject\ModelManager\Model\RowStructure; 20 | 21 | /** 22 | * PgEntity 23 | * 24 | * Entity converter. 25 | * It handles row types and composite types. 26 | * 27 | * @package ModelManager 28 | * @copyright 2014 - 2015 Grégoire HUBERT 29 | * @author Grégoire HUBERT 30 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 31 | * @see ConverterInterface 32 | */ 33 | class PgEntity implements ConverterInterface 34 | { 35 | protected $row_structure; 36 | protected $identity_mapper; 37 | protected $flexible_entity_class; 38 | 39 | 40 | /** 41 | * Constructor. 42 | * 43 | * @access public 44 | * @param $flexible_entity_class 45 | * @param RowStructure $structure 46 | * @param IdentityMapper $identity_mapper 47 | */ 48 | public function __construct( 49 | $flexible_entity_class, 50 | RowStructure $structure, 51 | IdentityMapper $identity_mapper = null 52 | ) { 53 | $this->flexible_entity_class = $flexible_entity_class; 54 | $this->row_structure = $structure; 55 | $this->identity_mapper = $identity_mapper === null 56 | ? new IdentityMapper() 57 | : $identity_mapper 58 | ; 59 | } 60 | 61 | /** 62 | * fromPg 63 | * 64 | * Embeddable entities are converted here. 65 | * 66 | * @see ConverterInterface 67 | */ 68 | public function fromPg($data, $type, Session $session) 69 | { 70 | if (empty($data)) { 71 | return null; 72 | } 73 | 74 | $data = trim($data, '()'); 75 | 76 | if ($type instanceof Projection) { 77 | $projection = $type; 78 | } else { 79 | $projection = new Projection( 80 | $this->flexible_entity_class, 81 | $this->row_structure->getDefinition() 82 | ); 83 | } 84 | 85 | $entity = (new HydrationPlan( 86 | $projection, 87 | $session 88 | ))->hydrate($this->transformData($data, $projection)); 89 | 90 | return $this->cacheEntity($entity); 91 | } 92 | 93 | /** 94 | * transformData 95 | * 96 | * Split data into an array prefixed with field names. 97 | * 98 | * @access private 99 | * @param string $data 100 | * @param Projection $projection 101 | * @return array 102 | */ 103 | private function transformData($data, Projection $projection) 104 | { 105 | $values = str_getcsv($data); 106 | $definition = $projection->getFieldNames(); 107 | $out_values = []; 108 | $values_count = count($values); 109 | 110 | for ($index = 0; $index < $values_count; $index++) { 111 | $out_values[$definition[$index]] = preg_match(':^{.*}$:', $values[$index]) 112 | ? stripcslashes($values[$index]) 113 | : $values[$index] 114 | ; 115 | } 116 | 117 | return $out_values; 118 | } 119 | 120 | /** 121 | * cacheEntity 122 | * 123 | * Check entity against the cache. 124 | * 125 | * @access public 126 | * @param FlexibleEntityInterface $entity 127 | * @return FlexibleEntityInterface 128 | */ 129 | public function cacheEntity(FlexibleEntityInterface $entity) 130 | { 131 | return $this 132 | ->identity_mapper 133 | ->fetch($entity, $this->row_structure->getPrimaryKey()) 134 | ; 135 | } 136 | 137 | /** 138 | * toPg 139 | * 140 | * @see ConverterInterface 141 | */ 142 | public function toPg($data, $type, Session $session) 143 | { 144 | if ($data === null) { 145 | return sprintf("NULL::%s", $type); 146 | } 147 | 148 | $fields = $this->getFields($data); 149 | $hydration_plan = $this->createHydrationPlan($session); 150 | 151 | return sprintf( 152 | "row(%s)::%s", 153 | join(',', $hydration_plan->dry($fields)), 154 | $type 155 | ); 156 | } 157 | 158 | /** 159 | * createHydrationPlan 160 | * 161 | * Create a new hydration plan. 162 | * 163 | * @access protected 164 | * @param Session $session 165 | * @return HydrationPlan 166 | */ 167 | protected function createHydrationPlan(Session $session) 168 | { 169 | return new HydrationPlan( 170 | new Projection($this->flexible_entity_class, $this->row_structure->getDefinition()), 171 | $session 172 | ); 173 | } 174 | 175 | /** 176 | * getFields 177 | * 178 | * Return the fields array. 179 | * 180 | * @access protected 181 | * @param mixed $data 182 | * @return array 183 | */ 184 | protected function getFields($data) 185 | { 186 | if (is_array($data)) { 187 | $fields = $data; 188 | } else { 189 | $this->checkData($data); 190 | $fields = $data->fields(); 191 | } 192 | 193 | return $fields; 194 | } 195 | 196 | /** 197 | * checkData 198 | * 199 | * Check if the given data is the right entity. 200 | * 201 | * @access protected 202 | * @param mixed $data 203 | * @throws ConverterException 204 | * @return PgEntity $this 205 | */ 206 | protected function checkData($data) 207 | { 208 | if (!$data instanceof $this->flexible_entity_class) { 209 | throw new ConverterException( 210 | sprintf( 211 | "This converter only knows how to convert entities of type '%s' ('%s' given).", 212 | $this->flexible_entity_class, 213 | get_class($data) 214 | ) 215 | ); 216 | } 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * toPgStandardFormat 223 | * 224 | * @see ConverterInterface 225 | */ 226 | public function toPgStandardFormat($data, $type, Session $session) 227 | { 228 | if ($data === null) { 229 | return null; 230 | } 231 | 232 | $fields = $this->getFields($data); 233 | 234 | return 235 | sprintf("(%s)", 236 | join(',', array_map(function ($val) { 237 | if ($val === null) { 238 | return ''; 239 | } elseif ($val === '') { 240 | return '""'; 241 | } elseif (preg_match('/[,\s]/', $val)) { 242 | return sprintf('"%s"', str_replace('"', '""', $val)); 243 | } else { 244 | return $val; 245 | } 246 | }, $this->createHydrationPlan($session)->freeze($fields) 247 | )) 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /sources/lib/Exception/GeneratorException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Exception; 11 | 12 | /** 13 | * GeneratorException 14 | * 15 | * Exception class for generators. 16 | * 17 | * @package ModelManager 18 | * @copyright 2014 - 2015 Grégoire HUBERT 19 | * @author Grégoire HUBERT 20 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 21 | * @see ModelException 22 | */ 23 | class GeneratorException extends ModelException 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /sources/lib/Exception/ModelException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Exception; 11 | 12 | use PommProject\Foundation\Exception\PommException; 13 | 14 | /** 15 | * ModelException 16 | * 17 | * Exception for the Model package 18 | * 19 | * @package ModelManager 20 | * @copyright 2014 - 2015 Grégoire HUBERT 21 | * @author Grégoire HUBERT 22 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 23 | * @see PommException 24 | */ 25 | class ModelException extends PommException 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /sources/lib/Exception/ModelLayerException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Exception; 11 | 12 | use PommProject\Foundation\Exception\PommException; 13 | 14 | /** 15 | * ModelLayerException 16 | * 17 | * Exception for the Model layer package 18 | * 19 | * @package ModelManager 20 | * @copyright 2014 - 2015 Grégoire HUBERT 21 | * @author Grégoire HUBERT 22 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 23 | * @see PommException 24 | */ 25 | class ModelLayerException extends PommException 26 | { 27 | } 28 | -------------------------------------------------------------------------------- /sources/lib/Generator/BaseGenerator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Generator; 11 | 12 | use PommProject\Foundation\Inspector\Inspector; 13 | use PommProject\Foundation\ParameterHolder; 14 | use PommProject\ModelManager\Exception\GeneratorException; 15 | use PommProject\ModelManager\Session; 16 | 17 | /** 18 | * BaseGenerator 19 | * 20 | * Base class for Generator 21 | * 22 | * @package ModelManager 23 | * @copyright 2014 - 2015 Grégoire HUBERT 24 | * @author Grégoire HUBERT 25 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 26 | * @abstract 27 | */ 28 | abstract class BaseGenerator 29 | { 30 | /** 31 | * @var Session 32 | */ 33 | private $session; 34 | 35 | protected $schema; 36 | protected $relation; 37 | protected $filename; 38 | protected $namespace; 39 | protected $flexible_container; 40 | 41 | 42 | /** 43 | * Constructor 44 | * 45 | * @access public 46 | * @param Session $session 47 | * @param string $schema 48 | * @param string $relation 49 | * @param string $filename 50 | * @param string $namespace 51 | * @param $flexible_container 52 | */ 53 | public function __construct(Session $session, $schema, $relation, $filename, $namespace, $flexible_container = null) 54 | { 55 | $this->session = $session; 56 | $this->schema = $schema; 57 | $this->relation = $relation; 58 | $this->filename = $filename; 59 | $this->namespace = $namespace; 60 | $this->flexible_container = $flexible_container; 61 | } 62 | 63 | /** 64 | * outputFileCreation 65 | * 66 | * Output what the generator will do. 67 | * 68 | * @access protected 69 | * @param array $output 70 | * @return BaseGenerator $this 71 | */ 72 | protected function outputFileCreation(array &$output) 73 | { 74 | if (file_exists($this->filename)) { 75 | $output[] = ['status' => 'ok', 'operation' => 'overwriting', 'file' => $this->filename]; 76 | } else { 77 | $output[] = ['status' => 'ok', 'operation' => 'creating', 'file' => $this->filename]; 78 | } 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * setSession 85 | * 86 | * Set the session. 87 | * 88 | * @access protected 89 | * @param Session $session 90 | * @return BaseGenerator $this 91 | */ 92 | protected function setSession(Session $session) 93 | { 94 | $this->session = $session; 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * getSession 101 | * 102 | * Return the session is set. Throw an exception otherwise. 103 | * 104 | * @access protected 105 | * @throws GeneratorException 106 | * @return Session 107 | */ 108 | protected function getSession() 109 | { 110 | if ($this->session === null) { 111 | throw new GeneratorException(sprintf("Session is not set.")); 112 | } 113 | 114 | return $this->session; 115 | } 116 | 117 | /** 118 | * getInspector 119 | * 120 | * Shortcut to session's inspector client. 121 | * 122 | * @access protected 123 | * @return Inspector 124 | */ 125 | protected function getInspector() 126 | { 127 | return $this->getSession()->getClientUsingPooler('inspector', null); 128 | } 129 | 130 | /** 131 | * generate 132 | * 133 | * Called to generate the file. 134 | * Possible options are: 135 | * * force: true if files can be overwritten, false otherwise 136 | * 137 | * @access public 138 | * @param ParameterHolder $input 139 | * @param array $output 140 | * @throws GeneratorException 141 | * @return array $output 142 | */ 143 | abstract public function generate(ParameterHolder $input, array $output = []); 144 | 145 | /** 146 | * getCodeTemplate 147 | * 148 | * Return the code template for files to be generated. 149 | * 150 | * @access protected 151 | * @return string 152 | */ 153 | abstract protected function getCodeTemplate(); 154 | 155 | /** 156 | * mergeTemplate 157 | * 158 | * Merge templates with given values. 159 | * 160 | * @access protected 161 | * @param array $variables 162 | * @return string 163 | */ 164 | protected function mergeTemplate(array $variables) 165 | { 166 | $prepared_variables = []; 167 | foreach ($variables as $name => $value) { 168 | $prepared_variables[sprintf("{:%s:}", $name)] = $value; 169 | } 170 | 171 | return strtr( 172 | $this->getCodeTemplate(), 173 | $prepared_variables 174 | ); 175 | } 176 | 177 | /** 178 | * saveFile 179 | * 180 | * Write the generated content to a file. 181 | * 182 | * @access protected 183 | * @param string $filename 184 | * @param string $content 185 | * @throws GeneratorException 186 | * @return BaseGenerator $this 187 | */ 188 | protected function saveFile($filename, $content) 189 | { 190 | if (!file_exists(dirname($filename)) 191 | && mkdir(dirname($filename), 0777, true) === false 192 | ) { 193 | throw new GeneratorException( 194 | sprintf( 195 | "Could not create directory '%s'.", 196 | dirname($filename) 197 | ) 198 | ); 199 | } 200 | 201 | if (file_put_contents($filename, $content) === false) { 202 | throw new GeneratorException( 203 | sprintf( 204 | "Could not open '%s' for writing.", 205 | $filename 206 | ) 207 | ); 208 | } 209 | 210 | return $this; 211 | } 212 | 213 | /** 214 | * checkOverwrite 215 | * 216 | * Check if the file exists and if it the write is forced. 217 | * 218 | * @access protected 219 | * @param ParameterHolder $input 220 | * @throws GeneratorException 221 | * @return BaseGenerator $this 222 | */ 223 | protected function checkOverwrite(ParameterHolder $input) 224 | { 225 | if (file_exists($this->filename) && $input->getParameter('force') !== true) { 226 | throw new GeneratorException(sprintf("Cannot overwrite file '%s' without --force option.", $this->filename)); 227 | } 228 | 229 | return $this; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /sources/lib/Generator/EntityGenerator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Generator; 11 | 12 | use PommProject\Foundation\Inflector; 13 | use PommProject\Foundation\ParameterHolder; 14 | 15 | /** 16 | * EntityGenerator 17 | * 18 | * Entity generator. 19 | * 20 | * @package ModelManager 21 | * @copyright 2014 - 2015 Grégoire HUBERT 22 | * @author Grégoire HUBERT 23 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 24 | * @see BaseGenerator 25 | */ 26 | class EntityGenerator extends BaseGenerator 27 | { 28 | /** 29 | * generate 30 | * 31 | * Generate Entity file. 32 | * 33 | * @see BaseGenerator 34 | */ 35 | public function generate(ParameterHolder $input, array $output = []) 36 | { 37 | $this 38 | ->checkOverwrite($input) 39 | ->outputFileCreation($output) 40 | ->saveFile( 41 | $this->filename, 42 | $this->mergeTemplate( 43 | [ 44 | 'namespace' => $this->namespace, 45 | 'entity' => Inflector::studlyCaps($this->relation), 46 | 'relation' => $this->relation, 47 | 'schema' => $this->schema, 48 | 'flexible_container' => $this->flexible_container, 49 | 'flexible_container_class' => array_reverse(explode('\\', $this->flexible_container))[0] 50 | ] 51 | ) 52 | ); 53 | 54 | return $output; 55 | } 56 | 57 | /** 58 | * getCodeTemplate 59 | * 60 | * @see BaseGenerator 61 | */ 62 | protected function getCodeTemplate() 63 | { 64 | return <<<'_' 65 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Generator; 11 | 12 | use PommProject\Foundation\Inflector; 13 | use PommProject\Foundation\ParameterHolder; 14 | use PommProject\Foundation\Where; 15 | use PommProject\ModelManager\Exception\GeneratorException; 16 | 17 | /** 18 | * ModelGenerator 19 | * 20 | * Generate a new model file. 21 | * If the given file already exist, it needs the force option to be set at 22 | * 'yes'. 23 | * 24 | * @package ModelManager 25 | * @copyright 2014 - 2015 Grégoire HUBERT 26 | * @author Grégoire HUBERT 27 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 28 | */ 29 | class ModelGenerator extends BaseGenerator 30 | { 31 | /** 32 | * generate 33 | * 34 | * Generate structure file. 35 | * 36 | * @see BaseGenerator 37 | */ 38 | public function generate(ParameterHolder $input, array $output = []) 39 | { 40 | $schema_oid = $this 41 | ->getSession() 42 | ->getInspector() 43 | ->getSchemaOid($this->schema); 44 | 45 | if ($schema_oid === null) { 46 | throw new GeneratorException(sprintf("Schema '%s' does not exist.", $this->schema)); 47 | } 48 | 49 | $relations_info = $this 50 | ->getSession() 51 | ->getInspector() 52 | ->getSchemaRelations($schema_oid, new Where('cl.relname = $*', [$this->relation])) 53 | ; 54 | 55 | if ($relations_info->isEmpty()) { 56 | throw new GeneratorException(sprintf("Relation '%s.%s' does not exist.", $this->schema, $this->relation)); 57 | } 58 | 59 | $this 60 | ->checkOverwrite($input) 61 | ->outputFileCreation($output) 62 | ->saveFile( 63 | $this->filename, 64 | $this->mergeTemplate( 65 | [ 66 | 'entity' => Inflector::studlyCaps($this->relation), 67 | 'namespace' => trim($this->namespace, '\\'), 68 | 'trait' => $relations_info->current()['type'] === 'table' ? 'WriteQueries' : 'ReadQueries', 69 | 'relation_type' => $relations_info->current()['type'], 70 | 'relation' => $this->relation 71 | ] 72 | ) 73 | ); 74 | 75 | return $output; 76 | } 77 | 78 | /** 79 | * getCodeTemplate 80 | * 81 | * @see BaseGenerator 82 | */ 83 | protected function getCodeTemplate() 84 | { 85 | return <<<'_' 86 | structure = new {:entity:}Structure; 120 | $this->flexible_entity_class = '\{:namespace:}\{:entity:}'; 121 | } 122 | } 123 | 124 | _; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /sources/lib/Generator/StructureGenerator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Generator; 11 | 12 | use PommProject\Foundation\ConvertedResultIterator; 13 | use PommProject\Foundation\Inflector; 14 | use PommProject\Foundation\ParameterHolder; 15 | use PommProject\ModelManager\Exception\GeneratorException; 16 | 17 | /** 18 | * StructureGenerator 19 | * 20 | * Generate a RowStructure file from relation inspection. 21 | * 22 | * @package ModelManager 23 | * @copyright 2014 - 2015 Grégoire HUBERT 24 | * @author Grégoire HUBERT 25 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 26 | */ 27 | class StructureGenerator extends BaseGenerator 28 | { 29 | /** 30 | * generate 31 | * 32 | * Generate structure file. 33 | * 34 | * @see BaseGenerator 35 | */ 36 | public function generate(ParameterHolder $input, array $output = []) 37 | { 38 | $table_oid = $this->checkRelationInformation(); 39 | $field_information = $this->getFieldInformation($table_oid); 40 | $primary_key = $this->getPrimaryKey($table_oid); 41 | $table_comment = $this->getTableComment($table_oid); 42 | 43 | if ($table_comment === null) { 44 | $table_comment = <<outputFileCreation($output) 53 | ->saveFile( 54 | $this->filename, 55 | $this->mergeTemplate( 56 | [ 57 | 'namespace' => $this->namespace, 58 | 'class_name' => $input->getParameter('class_name', Inflector::studlyCaps($this->relation)), 59 | 'relation' => sprintf("%s.%s", $this->schema, $this->relation), 60 | 'primary_key' => join( 61 | ', ', 62 | array_map( 63 | function ($val) { return sprintf("'%s'", $val); }, 64 | $primary_key 65 | ) 66 | ), 67 | 'add_fields' => $this->formatAddFields($field_information), 68 | 'table_comment' => $this->createPhpDocBlockFromText($table_comment), 69 | 'fields_comment' => $this->formatFieldsComment($field_information), 70 | ] 71 | ) 72 | ); 73 | 74 | return $output; 75 | } 76 | 77 | /** 78 | * formatAddFields 79 | * 80 | * Format 'addField' method calls. 81 | * 82 | * @access protected 83 | * @param ConvertedResultIterator $field_information 84 | * @return string 85 | */ 86 | protected function formatAddFields(ConvertedResultIterator $field_information) 87 | { 88 | $strings = []; 89 | 90 | foreach ($field_information as $info) { 91 | if (preg_match('/^(?:(.*)\.)?_(.*)$/', $info['type'], $matches)) { 92 | if ($matches[1] !== '') { 93 | $info['type'] = sprintf("%s.%s[]", $matches[1], $matches[2]); 94 | } else { 95 | $info['type'] = $matches[2].'[]'; 96 | } 97 | } 98 | 99 | $strings[] = sprintf( 100 | " ->addField('%s', '%s')", 101 | $info['name'], 102 | $info['type'] 103 | ); 104 | } 105 | 106 | return join("\n", $strings); 107 | } 108 | 109 | /** 110 | * formatFieldsComment 111 | * 112 | * Format fields comment to be in the class comment. This is because there 113 | * can be very long comments or comments with carriage returns. It is 114 | * furthermore more convenient to get all the descriptions in the head of 115 | * the generated class. 116 | * 117 | * @access protected 118 | * @param ConvertedResultIterator $field_information 119 | * @return string 120 | */ 121 | protected function formatFieldsComment(ConvertedResultIterator $field_information) 122 | { 123 | $comments = []; 124 | foreach ($field_information as $info) { 125 | if ($info['comment'] === null) { 126 | continue; 127 | } 128 | 129 | $comments[] = sprintf(" * %s:", $info['name']); 130 | $comments[] = $this->createPhpDocBlockFromText($info['comment']); 131 | } 132 | 133 | return count($comments) > 0 ? join("\n", $comments) : ' *'; 134 | } 135 | 136 | /** 137 | * createPhpDocBlockFromText 138 | * 139 | * Format a text into a PHPDoc comment block. 140 | * 141 | * @access protected 142 | * @param string $text 143 | * @return string 144 | */ 145 | protected function createPhpDocBlockFromText($text) 146 | { 147 | return join( 148 | "\n", 149 | array_map( 150 | function ($line) { return ' * '.$line; }, 151 | explode("\n", wordwrap($text)) 152 | ) 153 | ); 154 | } 155 | 156 | /** 157 | * checkRelationInformation 158 | * 159 | * Check if the given schema and relation exist. If so, the table oid is 160 | * returned, otherwise a GeneratorException is thrown. 161 | * 162 | * @access private 163 | * @throws GeneratorException 164 | * @return int $oid 165 | */ 166 | private function checkRelationInformation() 167 | { 168 | if ($this->getInspector()->getSchemaOid($this->schema) === null) { 169 | throw new GeneratorException(sprintf("Schema '%s' not found.", $this->schema)); 170 | } 171 | 172 | $table_oid = $this->getInspector()->getTableOid($this->schema, $this->relation); 173 | 174 | if ($table_oid === null) { 175 | throw new GeneratorException( 176 | sprintf( 177 | "Relation '%s' could not be found in schema '%s'.", 178 | $this->relation, 179 | $this->schema 180 | ) 181 | ); 182 | } 183 | 184 | return $table_oid; 185 | } 186 | 187 | /** 188 | * getFieldInformation 189 | * 190 | * Fetch a table field information. 191 | * 192 | * @access protected 193 | * @param int $table_oid 194 | * @throws GeneratorException 195 | * @return ConvertedResultIterator $fields_info 196 | */ 197 | protected function getFieldInformation($table_oid) 198 | { 199 | $fields_info = $this 200 | ->getInspector() 201 | ->getTableFieldInformation($table_oid) 202 | ; 203 | 204 | if ($fields_info === null) { 205 | throw new GeneratorException( 206 | sprintf( 207 | "Error while fetching fields information for table oid '%s'.", 208 | $table_oid 209 | ) 210 | ); 211 | } 212 | 213 | return $fields_info; 214 | } 215 | 216 | /** 217 | * getPrimaryKey 218 | * 219 | * Return the primary key of a relation if any. 220 | * 221 | * @access protected 222 | * @param string $table_oid 223 | * @return array $primary_key 224 | */ 225 | protected function getPrimaryKey($table_oid) 226 | { 227 | $primary_key = $this 228 | ->getInspector() 229 | ->getPrimaryKey($table_oid) 230 | ; 231 | 232 | return $primary_key; 233 | } 234 | 235 | /** 236 | * getTableComment 237 | * 238 | * Grab table comment from database. 239 | * 240 | * @access protected 241 | * @param int $table_oid 242 | * @return string|null 243 | */ 244 | protected function getTableComment($table_oid) 245 | { 246 | $comment = $this 247 | ->getInspector() 248 | ->getTableComment($table_oid) 249 | ; 250 | 251 | return $comment; 252 | } 253 | 254 | /** 255 | * getCodeTemplate 256 | * 257 | * @see BaseGenerator 258 | */ 259 | protected function getCodeTemplate() 260 | { 261 | return <<<'_' 262 | setRelation('{:relation:}') 296 | ->setPrimaryKey([{:primary_key:}]) 297 | {:add_fields:} 298 | ; 299 | } 300 | } 301 | 302 | _; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /sources/lib/Model/CollectionIterator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\Foundation\ResultIterator; 13 | use PommProject\Foundation\Session\ResultHandler; 14 | use PommProject\Foundation\Session\Session; 15 | use PommProject\ModelManager\Converter\PgEntity; 16 | use PommProject\ModelManager\Exception\ModelException; 17 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 18 | 19 | /** 20 | * CollectionIterator 21 | * 22 | * Iterator for query results. 23 | * 24 | * @package ModelManager 25 | * @copyright 2014 - 2015 Grégoire HUBERT 26 | * @author Grégoire HUBERT 27 | * @license MIT/X11 {@link http://opensource.org/licenses/mit-license.php} 28 | */ 29 | class CollectionIterator extends ResultIterator 30 | { 31 | /** 32 | * @var Session 33 | */ 34 | protected $session; 35 | 36 | /** 37 | * @var Projection 38 | */ 39 | protected $projection; 40 | 41 | /** 42 | * @var array 43 | */ 44 | protected $filters = []; 45 | 46 | /** 47 | * @var HydrationPlan 48 | */ 49 | protected $hydration_plan; 50 | 51 | /** 52 | * @var PgEntity 53 | */ 54 | private $entity_converter; 55 | 56 | /** 57 | * __construct 58 | * 59 | * Constructor 60 | * 61 | * @access public 62 | * @param ResultHandler $result 63 | * @param Session $session 64 | * @param Projection $projection 65 | */ 66 | public function __construct(ResultHandler $result, Session $session, Projection $projection) 67 | { 68 | parent::__construct($result); 69 | $this->projection = $projection; 70 | $this->session = $session; 71 | $this->hydration_plan = new HydrationPlan($projection, $session); 72 | $this->entity_converter = $this 73 | ->session 74 | ->getClientUsingPooler('converter', $this->projection->getFlexibleEntityClass()) 75 | ->getConverter() 76 | ; 77 | } 78 | 79 | /** 80 | * get 81 | * 82 | * @see ResultIterator 83 | * @return FlexibleEntityInterface 84 | */ 85 | public function get($index) 86 | { 87 | return $this->parseRow(parent::get($index)); 88 | } 89 | 90 | /** 91 | * parseRow 92 | * 93 | * Convert values from Pg. 94 | * 95 | * @access protected 96 | * @param array $values 97 | * @return FlexibleEntityInterface 98 | * @see ResultIterator 99 | */ 100 | public function parseRow(array $values) 101 | { 102 | $values = $this->launchFilters($values); 103 | $entity = $this->hydration_plan->hydrate($values); 104 | 105 | return $this->entity_converter->cacheEntity($entity); 106 | } 107 | 108 | /** 109 | * launchFilters 110 | * 111 | * Launch filters on the given values. 112 | * 113 | * @access protected 114 | * @param array $values 115 | * @throws ModelException if return is not an array. 116 | * @return array 117 | */ 118 | protected function launchFilters(array $values) 119 | { 120 | foreach ($this->filters as $filter) { 121 | $values = call_user_func($filter, $values); 122 | 123 | if (!is_array($values)) { 124 | throw new ModelException(sprintf("Filter error. Filters MUST return an array of values.")); 125 | } 126 | } 127 | 128 | return $values; 129 | } 130 | 131 | /** 132 | * registerFilter 133 | * 134 | * Register a new callable filter. All filters MUST return an associative 135 | * array with field name as key. 136 | * 137 | * @access public 138 | * @param callable $callable the filter. 139 | * @return CollectionIterator $this 140 | * @throws ModelException 141 | */ 142 | public function registerFilter($callable) 143 | { 144 | if (!is_callable($callable)) { 145 | throw new ModelException(sprintf( 146 | "Given filter is not a callable (type '%s').", 147 | gettype($callable) 148 | )); 149 | } 150 | 151 | $this->filters[] = $callable; 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * clearFilters 158 | * 159 | * Empty the filter stack. 160 | */ 161 | public function clearFilters() 162 | { 163 | $this->filters = []; 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * extract 170 | * 171 | * Return an array of entities extracted as arrays. 172 | * 173 | * @access public 174 | * @return array 175 | */ 176 | public function extract() 177 | { 178 | $results = []; 179 | 180 | foreach ($this as $result) { 181 | $results[] = $result->extract(); 182 | } 183 | 184 | return $results; 185 | } 186 | 187 | /** 188 | * slice 189 | * 190 | * see @ResultIterator 191 | * 192 | * @access public 193 | * @param string $name 194 | * @return array 195 | */ 196 | public function slice($name) 197 | { 198 | return $this->convertSlice(parent::slice($name), $name); 199 | } 200 | 201 | 202 | /** 203 | * convertSlice 204 | * 205 | * Convert a slice. 206 | * 207 | * @access protected 208 | * @param array $values 209 | * @param string $name 210 | * @return array 211 | */ 212 | protected function convertSlice(array $values, $name) 213 | { 214 | $type = $this->projection->getFieldType($name); 215 | $converter = $this->hydration_plan->getConverterForField($name); 216 | 217 | return array_map( 218 | function ($val) use ($converter, $type) { 219 | return $converter->fromPg($val, $type, $this->session); 220 | }, 221 | $values 222 | ); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /sources/lib/Model/FlexibleEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\Foundation\Inflector; 13 | use PommProject\ModelManager\Exception\ModelException; 14 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleContainer; 15 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 16 | 17 | /** 18 | * FlexibleEntity 19 | * 20 | * Parent for entity classes. 21 | * 22 | * @abstract 23 | * @package ModelManager 24 | * @copyright 2014 - 2015 Grégoire HUBERT 25 | * @author Grégoire HUBERT 26 | * @license MIT/X11 {@link http://opensource.org/licenses/mit-license.php} 27 | */ 28 | abstract class FlexibleEntity extends FlexibleContainer implements \ArrayAccess 29 | { 30 | public static $strict = true; 31 | protected static $has_methods; 32 | 33 | /** 34 | * __construct 35 | * 36 | * Instantiate the entity and hydrate it with the given values. 37 | * 38 | * @access public 39 | * @param array $values Optional starting values. 40 | */ 41 | public function __construct(array $values = null) 42 | { 43 | if ($values !== null) { 44 | $this->hydrate($values); 45 | } 46 | } 47 | 48 | /** 49 | * get 50 | * 51 | * Returns the $var value 52 | * 53 | * @final 54 | * @access public 55 | * @param string|array $var Key(s) you want to retrieve value from. 56 | * @throws ModelException if strict and the attribute does not exist. 57 | * @return mixed 58 | */ 59 | final public function get($var) 60 | { 61 | if (is_scalar($var)) { 62 | if ($this->has($var)) { 63 | return $this->container[$var]; 64 | } elseif (static::$strict === true) { 65 | throw new ModelException(sprintf("No such key '%s'.", $var)); 66 | } 67 | } elseif (is_array($var)) { 68 | return array_intersect_key($this->container, array_flip($var)); 69 | } 70 | } 71 | 72 | /** 73 | * has 74 | * 75 | * Returns true if the given key exists. 76 | * 77 | * @final 78 | * @access public 79 | * @param string $var 80 | * @return boolean 81 | */ 82 | final public function has($var) 83 | { 84 | return isset($this->container[$var]) || array_key_exists($var, $this->container); 85 | } 86 | 87 | /** 88 | * set 89 | * 90 | * Set a value in the var holder. 91 | * 92 | * @final 93 | * @access public 94 | * @param String $var Attribute name. 95 | * @param Mixed $value Attribute value. 96 | * @return FlexibleEntity $this 97 | */ 98 | final public function set($var, $value) 99 | { 100 | $this->container[$var] = $value; 101 | $this->touch(); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * add 108 | * 109 | * When the corresponding attribute is an array, call this method 110 | * to set values. 111 | * 112 | * @access public 113 | * @param string $var 114 | * @param mixed $value 115 | * @return FlexibleEntity $this 116 | * @throws ModelException 117 | */ 118 | public function add($var, $value) 119 | { 120 | if ($this->has($var)) { 121 | if (is_array($this->container[$var])) { 122 | $this->container[$var][] = $value; 123 | } else { 124 | throw new ModelException(sprintf("Field '%s' exists and is not an array.", $var)); 125 | } 126 | } else { 127 | $this->container[$var] = [$value]; 128 | } 129 | $this->touch(); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * clear 136 | * 137 | * Drop an attribute from the var holder. 138 | * 139 | * @final 140 | * @access public 141 | * @param String $offset Attribute name. 142 | * @return FlexibleEntity $this 143 | */ 144 | final public function clear($offset) 145 | { 146 | if ($this->has($offset)) { 147 | unset($this->container[$offset]); 148 | $this->touch(); 149 | } 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * __call 156 | * 157 | * Allows dynamic methods getXxx, setXxx, hasXxx, addXxx or clearXxx. 158 | * 159 | * @access public 160 | * @throws ModelException if method does not exist. 161 | * @param mixed $method 162 | * @param mixed $arguments 163 | * @return mixed 164 | */ 165 | public function __call($method, $arguments) 166 | { 167 | list($operation, $attribute) = $this->extractMethodName($method); 168 | 169 | switch ($operation) { 170 | case 'set': 171 | return $this->set($attribute, $arguments[0]); 172 | case 'get': 173 | return $this->get($attribute); 174 | case 'add': 175 | return $this->add($attribute, $arguments[0]); 176 | case 'has': 177 | return $this->has($attribute); 178 | case 'clear': 179 | return $this->clear($attribute); 180 | default: 181 | throw new ModelException(sprintf('No such method "%s:%s()"', get_class($this), $method)); 182 | } 183 | } 184 | 185 | /** 186 | * convert 187 | * 188 | * Make all keys lowercase and hydrate the object. 189 | * 190 | * @access public 191 | * @param Array $values 192 | * @return FlexibleEntity 193 | */ 194 | public function convert(array $values) 195 | { 196 | $tmp = []; 197 | 198 | foreach ($values as $key => $value) { 199 | $tmp[strtolower($key)] = $value; 200 | } 201 | 202 | return $this->hydrate($tmp); 203 | } 204 | 205 | /** 206 | * extract 207 | * 208 | * Returns the fields flatten as arrays. 209 | * 210 | * The complex stuff in here is when there is an array, since all elements 211 | * in arrays are the same type, we check only its first value to know if we need 212 | * to traverse it or not. 213 | * 214 | * @see FlexibleEntityInterface 215 | */ 216 | public function extract() 217 | { 218 | $array_recurse = function ($val) use (&$array_recurse) { 219 | if (is_scalar($val)) { 220 | return $val; 221 | } 222 | 223 | if (is_array($val)) { 224 | if (is_array(current($val)) || (is_object(current($val)) && current($val) instanceof FlexibleEntityInterface)) { 225 | return array_map($array_recurse, $val); 226 | } else { 227 | return $val; 228 | } 229 | } 230 | 231 | if (is_object($val) && $val instanceof FlexibleEntityInterface) { 232 | return $val->extract(); 233 | } 234 | 235 | return $val; 236 | }; 237 | 238 | 239 | return array_map($array_recurse, array_merge($this->container, $this->getCustomFields())); 240 | } 241 | 242 | /** 243 | * getCustomFields 244 | * 245 | * Return a list of custom methods with has() accessor. 246 | * 247 | * @access private 248 | * @return array 249 | */ 250 | private function getCustomFields() 251 | { 252 | if (static::$has_methods === null) { 253 | static::fillHasMethods($this); 254 | } 255 | 256 | $custom_fields = []; 257 | 258 | foreach (static::$has_methods as $method) { 259 | if (call_user_func([$this, sprintf("has%s", $method)]) === true) { 260 | $custom_fields[Inflector::underscore(lcfirst($method))] = call_user_func([$this, sprintf("get%s", $method)]); 261 | } 262 | } 263 | 264 | return $custom_fields; 265 | } 266 | 267 | /** 268 | * getIterator 269 | * 270 | * @see FlexibleEntityInterface 271 | */ 272 | public function getIterator() 273 | { 274 | return new \ArrayIterator(array_merge($this->container, $this->getCustomFields())); 275 | } 276 | 277 | /** 278 | * __set 279 | * 280 | * PHP magic to set attributes. 281 | * 282 | * @access public 283 | * @param String $var Attribute name. 284 | * @param Mixed $value Attribute value. 285 | * @return FlexibleEntity $this 286 | */ 287 | public function __set($var, $value) 288 | { 289 | $method_name = "set".Inflector::studlyCaps($var); 290 | $this->$method_name($value); 291 | 292 | return $this; 293 | } 294 | 295 | /** 296 | * __get 297 | * 298 | * PHP magic to get attributes. 299 | * 300 | * @access public 301 | * @param String $var Attribute name. 302 | * @return Mixed Attribute value. 303 | */ 304 | public function __get($var) 305 | { 306 | $method_name = "get".Inflector::studlyCaps($var); 307 | 308 | return $this->$method_name(); 309 | } 310 | 311 | /** 312 | * __isset 313 | * 314 | * Easy value check. 315 | * 316 | * @access public 317 | * @param string $var 318 | * @return bool 319 | */ 320 | public function __isset($var) 321 | { 322 | $method_name = "has".Inflector::studlyCaps($var); 323 | 324 | return $this->$method_name(); 325 | } 326 | 327 | /** 328 | * __unset 329 | * 330 | * Clear an attribute. 331 | * 332 | * @access public 333 | * @param string $var 334 | * @return FlexibleEntity $this 335 | */ 336 | public function __unset($var) 337 | { 338 | $method_name = "clear".Inflector::studlyCaps($var); 339 | 340 | return $this->$method_name(); 341 | } 342 | 343 | /** 344 | * {@inheritdoc} 345 | */ 346 | public function offsetExists($offset) 347 | { 348 | $method_name = "has".Inflector::studlyCaps($offset); 349 | 350 | return $this->$method_name(); 351 | } 352 | 353 | /** 354 | * {@inheritdoc} 355 | */ 356 | public function offsetSet($offset, $value) 357 | { 358 | $this->__set($offset, $value); 359 | } 360 | 361 | /** 362 | * {@inheritdoc} 363 | */ 364 | public function offsetGet($offset) 365 | { 366 | return $this->__get($offset); 367 | } 368 | 369 | /** 370 | * {@inheritdoc} 371 | */ 372 | public function offsetUnset($offset) 373 | { 374 | $this->clear($offset); 375 | } 376 | 377 | /** 378 | * fillHasMethods 379 | * 380 | * When getIterator is called the first time, the list of "has" methods is 381 | * set in a static attribute to boost performances. 382 | * 383 | * @access protected 384 | * @param FlexibleEntity $entity 385 | * @return null 386 | */ 387 | protected static function fillHasMethods(FlexibleEntity $entity) 388 | { 389 | static::$has_methods = []; 390 | 391 | foreach (get_class_methods($entity) as $method) { 392 | if (preg_match('/^has([A-Z].*)$/', $method, $matches)) { 393 | static::$has_methods[] = $matches[1]; 394 | } 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /sources/lib/Model/FlexibleEntity/FlexibleContainer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\FlexibleEntity; 11 | 12 | use PommProject\Foundation\Inflector; 13 | use PommProject\ModelManager\Exception\ModelException; 14 | 15 | /** 16 | * FlexibleContainerTrait 17 | * 18 | * Trait for being a flexible data container. 19 | * 20 | * @package ModelManager 21 | * @copyright 2014 - 2015 Grégoire HUBERT 22 | * @author Grégoire HUBERT 23 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 24 | */ 25 | abstract class FlexibleContainer implements FlexibleEntityInterface, \IteratorAggregate 26 | { 27 | use StatefulEntityTrait; 28 | 29 | protected $container = []; 30 | 31 | /** 32 | * hydrate 33 | * 34 | * @see FlexibleEntityInterface 35 | */ 36 | public function hydrate(array $values) 37 | { 38 | $this->container = array_merge($this->container, $values); 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * fields 45 | * 46 | * Return the fields array. If a given field does not exist, an exception 47 | * is thrown. 48 | * 49 | * @throws \InvalidArgumentException 50 | * @see FlexibleEntityInterface 51 | */ 52 | public function fields(array $fields = null) 53 | { 54 | if ($fields === null) { 55 | return $this->container; 56 | } 57 | 58 | $output = []; 59 | 60 | foreach ($fields as $name) { 61 | if (isset($this->container[$name]) || array_key_exists($name, $this->container)) { 62 | $output[$name] = $this->container[$name]; 63 | } else { 64 | throw new \InvalidArgumentException( 65 | sprintf( 66 | "No such field '%s'. Existing fields are {%s}", 67 | $name, 68 | join(', ', array_keys($this->container)) 69 | ) 70 | ); 71 | } 72 | } 73 | 74 | return $output; 75 | } 76 | 77 | 78 | /** 79 | * extract 80 | * 81 | * @see FlexibleEntityInterface 82 | */ 83 | public function extract() 84 | { 85 | return $this->fields(); 86 | } 87 | 88 | /** 89 | * getIterator 90 | * 91 | * @see FlexibleEntityInterface 92 | */ 93 | public function getIterator() 94 | { 95 | return new \ArrayIterator($this->extract()); 96 | } 97 | 98 | /** 99 | * __call 100 | * 101 | * Allows dynamic methods getXxx, setXxx, hasXxx or clearXxx. 102 | * 103 | * @access public 104 | * @throws ModelException if method does not exist. 105 | * @param mixed $method 106 | * @param mixed $arguments 107 | * @return mixed 108 | */ 109 | public function __call($method, $arguments) 110 | { 111 | list($operation, $attribute) = $this->extractMethodName($method); 112 | 113 | switch ($operation) { 114 | case 'set': 115 | $this->container[$attribute] = $arguments[0]; 116 | 117 | return $this; 118 | case 'get': 119 | return $this 120 | ->checkAttribute($attribute) 121 | ->container[$attribute] 122 | ; 123 | case 'has': 124 | return isset($this->container[$attribute]) || array_key_exists($attribute, $this->container); 125 | case 'clear': 126 | unset($this->checkAttribute($attribute)->container[$attribute]); 127 | 128 | return $this; 129 | default: 130 | throw new ModelException(sprintf('No such method "%s:%s()"', get_class($this), $method)); 131 | } 132 | } 133 | 134 | /** 135 | * checkAttribute 136 | * 137 | * Check if the attribute exist. Throw an exception if not. 138 | * 139 | * @access protected 140 | * @param string $attribute 141 | * @return FlexibleContainer $this 142 | * @throws ModelException 143 | */ 144 | protected function checkAttribute($attribute) 145 | { 146 | if (!(isset($this->container[$attribute]) || array_key_exists($attribute, $this->container))) { 147 | throw new ModelException( 148 | sprintf( 149 | "No such attribute '%s'. Available attributes are {%s}", 150 | $attribute, 151 | join(", ", array_keys($this->fields())) 152 | ) 153 | ); 154 | } 155 | 156 | return $this; 157 | } 158 | 159 | /** 160 | * extractMethodName 161 | * 162 | * Get container field name from method name. 163 | * It returns an array with the operation (get, set, etc.) as first member 164 | * and the name of the attribute as second member. 165 | * 166 | * @access protected 167 | * @param string $argument 168 | * @return array 169 | * @throws ModelException 170 | */ 171 | protected function extractMethodName($argument) 172 | { 173 | $split = preg_split('/(?=[A-Z])/', $argument, 2); 174 | 175 | if (count($split) !== 2) { 176 | throw new ModelException(sprintf('No such argument "%s:%s()"', get_class($this), $argument)); 177 | } 178 | 179 | return [$split[0], Inflector::underscore($split[1])]; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /sources/lib/Model/FlexibleEntity/FlexibleEntityInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\FlexibleEntity; 11 | 12 | /** 13 | * FlexibleEntityInterface 14 | * 15 | * @package ModelManager 16 | * @copyright 2014 - 2015 Grégoire HUBERT 17 | * @author Grégoire HUBERT 18 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 19 | */ 20 | interface FlexibleEntityInterface 21 | { 22 | /* 23 | * These constants reflect the status of the entity. 24 | * 25 | * When status is NONE, the entity neither exists in the database nor has 26 | * been modified since creation. 27 | * 28 | * When status is EXIST, the entity exists in the database. 29 | * 30 | * When status is MODIFIED, the entity has been modified since creation or 31 | * last persist operation. 32 | */ 33 | const STATUS_NONE = 0; 34 | const STATUS_EXIST = 1; 35 | const STATUS_MODIFIED = 2; 36 | 37 | /** 38 | * hydrate 39 | * 40 | * Set raw values in an entity. If some values are already set, they are 41 | * overridden with new values. 42 | * 43 | * @access public 44 | * @param array $fields 45 | * @return FlexibleEntityInterface 46 | */ 47 | public function hydrate(array $fields); 48 | 49 | /** 50 | * fields 51 | * 52 | * Return an array of entity raw values. An optional array can be passed 53 | * with the list of fields to retrieve. If the array is null, all fields 54 | * are returned. The case when a given field does not exist is left as 55 | * one's choice. 56 | * 57 | * @access public 58 | * @param array $fields 59 | * @return array 60 | */ 61 | public function fields(array $fields = null); 62 | 63 | /** 64 | * extract 65 | * 66 | * Return an array with a representation of the object values. It is mostly 67 | * used prior to a serialization in REST API or other string responses. 68 | * 69 | * @access public 70 | * @return array 71 | */ 72 | public function extract(); 73 | 74 | /** 75 | * status 76 | * 77 | * Return or set the current status of the instance. The status is a 78 | * bitmask of the different possible states an entity can have. 79 | * Status can be 80 | * FlexibleEntityInterface::STATUS_NONE = 0, 81 | * FlexibleEntityInterface::STATUS_EXIST = 1 82 | * FlexibleEntityInterface::STATUS_MODIFIED = 2 83 | * STATUS_EXIST + STATUS_MODIFIED = 3 84 | * @see https://github.com/pomm-project/ModelManager/issues/46#issuecomment-130650107 85 | * 86 | * If a status is specified, it sets the current entity's status and 87 | * returns itself. If no status are provided, it returns the current 88 | * status. 89 | * 90 | * @access public 91 | * @param int (null) 92 | * @return int|FlexibleEntityInterface 93 | */ 94 | public function status($status = null); 95 | } 96 | -------------------------------------------------------------------------------- /sources/lib/Model/FlexibleEntity/StatefulEntityTrait.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\FlexibleEntity; 11 | 12 | /** 13 | * StatefulEntityTrait 14 | * 15 | * Entities with the ability to keep record of their modification or 16 | * persistence status. 17 | * 18 | * @package ModelManager 19 | * @copyright 2014 - 2015 Grégoire HUBERT 20 | * @author Grégoire HUBERT 21 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 22 | * @see FlexibleEntityInterface 23 | */ 24 | trait StatefulEntityTrait 25 | { 26 | private $status = FlexibleEntityInterface::STATUS_NONE; 27 | 28 | /** 29 | * @see FlexibleEntityInterface 30 | */ 31 | public function status($status = null) 32 | { 33 | if ($status !== null) { 34 | $this->status = (int) $status; 35 | 36 | return $this; 37 | } 38 | 39 | return $this->status; 40 | } 41 | 42 | /** 43 | * touch 44 | * 45 | * Set the entity as modified. 46 | * 47 | * @access public 48 | * @return FlexibleEntityInterface 49 | */ 50 | public function touch() 51 | { 52 | $this->status |= FlexibleEntityInterface::STATUS_MODIFIED; 53 | 54 | return $this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sources/lib/Model/HydrationPlan.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\Foundation\Converter\ConverterClient; 14 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 15 | 16 | /** 17 | * HydrationPlan 18 | * 19 | * Tell the FlexibleEntityConverter how to hydrate fields. 20 | * 21 | * @package ModelManager 22 | * @copyright 2014 - 2015 Grégoire HUBERT 23 | * @author Grégoire HUBERT 24 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 25 | */ 26 | class HydrationPlan 27 | { 28 | protected $session; 29 | protected $projection; 30 | protected $converters = []; 31 | protected $field_types = []; 32 | 33 | 34 | /** 35 | * Construct 36 | * 37 | * @access public 38 | * @param Projection $projection 39 | * @param Session $session 40 | */ 41 | public function __construct(Projection $projection, Session $session) 42 | { 43 | $this->projection = $projection; 44 | $this->session = $session; 45 | 46 | $this->loadConverters(); 47 | } 48 | 49 | /** 50 | * loadConverters 51 | * 52 | * Cache converters needed for this result set. 53 | * 54 | * @access protected 55 | * @return HydrationPlan $this 56 | */ 57 | protected function loadConverters() 58 | { 59 | foreach ($this->projection as $name => $type) { 60 | if ($this->projection->isArray($name)) { 61 | $this->converters[$name] = $this 62 | ->session 63 | ->getClientUsingPooler('converter', 'array') 64 | ->getConverter() 65 | ; 66 | } else { 67 | $this->converters[$name] = $this 68 | ->session 69 | ->getClientUsingPooler('converter', $type) 70 | ->getConverter() 71 | ; 72 | } 73 | 74 | $this->field_types[$name] = $type; 75 | } 76 | 77 | return $this; 78 | } 79 | 80 | 81 | /** 82 | * getFieldType 83 | * 84 | * Return the type of the given field. Proxy to Projection::getFieldType(). 85 | * 86 | * @access public 87 | * @param string $name 88 | * @return string 89 | */ 90 | public function getFieldType($name) 91 | { 92 | return $this->projection->getFieldType($name); 93 | } 94 | 95 | /** 96 | * isArray 97 | * 98 | * Tell if the given field is an array or not. 99 | * 100 | * @access public 101 | * @param string $name 102 | * @return bool 103 | */ 104 | public function isArray($name) 105 | { 106 | return $this->projection->isArray($name); 107 | } 108 | 109 | 110 | /** 111 | * hydrate 112 | * 113 | * Take values fetched from the database, launch conversion system and 114 | * hydrate the FlexibleEntityInterface through the mapper. 115 | * 116 | * @access public 117 | * @param array $values 118 | * @return FlexibleEntityInterface 119 | */ 120 | public function hydrate(array $values) 121 | { 122 | $values = $this->convert('fromPg', $values); 123 | 124 | return $this->createEntity($values); 125 | } 126 | 127 | /** 128 | * dry 129 | * 130 | * Return values converted to Pg. 131 | * 132 | * @access public 133 | * @param array $values 134 | * @return array 135 | */ 136 | public function dry(array $values) 137 | { 138 | return $this->convert('toPg', $values); 139 | } 140 | 141 | /** 142 | * freeze 143 | * 144 | * Return values converted to Pg standard output. 145 | * 146 | * @access public 147 | * @param array $values 148 | * @return array converted values 149 | */ 150 | public function freeze(array $values) 151 | { 152 | return $this->convert('toPgStandardFormat', $values); 153 | } 154 | 155 | /** 156 | * convert 157 | * 158 | * Convert values from / to postgres. 159 | * 160 | * @access protected 161 | * @param string $from_to 162 | * @param array $values 163 | * @return array 164 | */ 165 | protected function convert($from_to, array $values) 166 | { 167 | $out_values = []; 168 | 169 | foreach ($values as $name => $value) { 170 | if (isset($this->converters[$name])) { 171 | $out_values[$name] = $this 172 | ->converters[$name] 173 | ->$from_to($value, $this->field_types[$name], $this->session) 174 | ; 175 | } else { 176 | $out_values[$name] = $value; 177 | } 178 | } 179 | 180 | return $out_values; 181 | } 182 | 183 | /** 184 | * createEntity 185 | * 186 | * Instantiate FlexibleEntityInterface from converted values. 187 | * 188 | * @access protected 189 | * @param array $values 190 | * @return FlexibleEntityInterface 191 | */ 192 | protected function createEntity(array $values) 193 | { 194 | $class = $this->projection->getFlexibleEntityClass(); 195 | 196 | return (new $class()) 197 | ->hydrate($values) 198 | ; 199 | } 200 | 201 | /** 202 | * getConverterForField 203 | * 204 | * Return the converter client associated with a field. 205 | * 206 | * @access public 207 | * @param string $field_name 208 | * @return ConverterClient 209 | */ 210 | public function getConverterForField($field_name) 211 | { 212 | if (!isset($this->converters[$field_name])) { 213 | throw new \RuntimeException( 214 | sprintf( 215 | "Error, '%s' field has no converters registered. Fields are {%s}.", 216 | $field_name, 217 | join(', ', array_keys($this->converters)) 218 | ) 219 | ); 220 | } 221 | 222 | return $this->converters[$field_name]; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /sources/lib/Model/IdentityMapper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 13 | 14 | /** 15 | * IdentityMapper 16 | * 17 | * Cache for FlexibleEntityInterface instances to ensure there are no different 18 | * instances for the same data. 19 | * 20 | * @package ModelManager 21 | * @copyright 2014 - 2015 Grégoire HUBERT 22 | * @author Grégoire HUBERT 23 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 24 | */ 25 | class IdentityMapper 26 | { 27 | /** 28 | * @var FlexibleEntityInterface[] 29 | */ 30 | protected $instances = []; 31 | 32 | /** 33 | * getSignature 34 | * 35 | * Compute a unique signature upon entity's values in its primary key. If 36 | * an empty primary key is provided, null is returned. 37 | * 38 | * @static 39 | * @access public 40 | * @param FlexibleEntityInterface $entity 41 | * @param array $primary_key 42 | * @return string 43 | */ 44 | public static function getSignature(FlexibleEntityInterface $entity, array $primary_key) 45 | { 46 | if (count($primary_key) === 0) { 47 | return null; 48 | } 49 | 50 | return sha1(sprintf("%s|%s", serialize($entity->fields($primary_key)), get_class($entity))); 51 | } 52 | 53 | /** 54 | * fetch 55 | * 56 | * Pool FlexibleEntityInterface instances and update them if necessary. 57 | * 58 | * @access public 59 | * @param FlexibleEntityInterface $entity 60 | * @param array $primary_key 61 | * @return FlexibleEntityInterface 62 | */ 63 | public function fetch(FlexibleEntityInterface $entity, array $primary_key) 64 | { 65 | $signature = self::getSignature($entity, $primary_key); 66 | 67 | if ($signature === null) { 68 | return $entity; 69 | } 70 | 71 | if (!array_key_exists($signature, $this->instances)) { 72 | $this->instances[$signature] = $entity; 73 | $entity->status(FlexibleEntityInterface::STATUS_EXIST); 74 | } else { 75 | $this->instances[$signature]->hydrate($entity->fields()); 76 | } 77 | 78 | return $this->instances[$signature]; 79 | } 80 | 81 | /** 82 | * clear 83 | * 84 | * Flush instances from the identity mapper. 85 | * 86 | * @access public 87 | * @return IdentityMapper $this 88 | */ 89 | public function clear() 90 | { 91 | $this->instances = []; 92 | 93 | return $this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /sources/lib/Model/Model.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\Foundation\Client\ClientInterface; 13 | use PommProject\Foundation\Session\Session; 14 | use PommProject\ModelManager\Converter\PgEntity; 15 | use PommProject\ModelManager\Exception\ModelException; 16 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 17 | 18 | /** 19 | * Model 20 | * 21 | * Base class for custom Model classes. 22 | * 23 | * @abstract 24 | * @package Pomm 25 | * @copyright 2014 - 2015 Grégoire HUBERT 26 | * @author Grégoire HUBERT 27 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 28 | * @see ClientInterface 29 | */ 30 | abstract class Model implements ClientInterface 31 | { 32 | protected $session; 33 | protected $flexible_entity_class; 34 | 35 | 36 | /** 37 | * @var RowStructure 38 | */ 39 | protected $structure; 40 | 41 | /** 42 | * getSession 43 | * 44 | * Return the current session. If session is not set, a ModelException is 45 | * thrown. 46 | * 47 | * @access public 48 | * @return Session 49 | * @throws ModelException 50 | */ 51 | public function getSession() 52 | { 53 | if ($this->session === null) { 54 | throw new ModelException(sprintf("Model class '%s' is not registered against the session.", get_class($this))); 55 | } 56 | 57 | return $this->session; 58 | } 59 | 60 | /** 61 | * getClientType 62 | * 63 | * @see ClientInterface 64 | */ 65 | public function getClientType() 66 | { 67 | return 'model'; 68 | } 69 | 70 | /** 71 | * getClientIdentifier 72 | * 73 | * @see ClientInterface 74 | */ 75 | public function getClientIdentifier() 76 | { 77 | return trim(get_class($this), "\\"); 78 | } 79 | 80 | /** 81 | * initialize 82 | * 83 | * @see ClientInterface 84 | */ 85 | public function initialize(Session $session) 86 | { 87 | $this->session = $session; 88 | 89 | if ($this->structure === null) { 90 | throw new ModelException(sprintf("Structure not set while initializing Model class '%s'.", get_class($this))); 91 | } 92 | 93 | if ($this->flexible_entity_class == null) { 94 | throw new ModelException(sprintf("Flexible entity not set while initializing Model class '%s'.", get_class($this))); 95 | } elseif (!(new \ReflectionClass($this->flexible_entity_class)) 96 | ->implementsInterface('\PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface') 97 | ) { 98 | throw new ModelException(sprintf("Flexible entity must implement FlexibleEntityInterface.")); 99 | } 100 | 101 | $session->getPoolerForType('converter') 102 | ->getConverterHolder() 103 | ->registerConverter( 104 | $this->flexible_entity_class, 105 | new PgEntity( 106 | $this->flexible_entity_class, 107 | $this->getStructure() 108 | ), 109 | [ 110 | $this->getStructure()->getRelation(), 111 | $this->flexible_entity_class, 112 | ] 113 | ); 114 | } 115 | 116 | /** 117 | * shutdown 118 | * 119 | * @see ClientInterface 120 | */ 121 | public function shutdown() 122 | { 123 | } 124 | 125 | /** 126 | * createEntity 127 | * 128 | * Create a new entity. 129 | * 130 | * @access public 131 | * @param array $values 132 | * @return FlexibleEntityInterface 133 | */ 134 | public function createEntity(array $values = []) 135 | { 136 | $class_name = $this->getFlexibleEntityClass(); 137 | 138 | return (new $class_name) 139 | ->hydrate($values) 140 | ; 141 | } 142 | 143 | /** 144 | * query 145 | * 146 | * Execute the given query and return a Collection iterator on results. If 147 | * no projections are passed, it will use the default projection using 148 | * createProjection() method. 149 | * 150 | * @access protected 151 | * @param string $sql 152 | * @param array $values 153 | * @param Projection $projection 154 | * @return CollectionIterator 155 | */ 156 | protected function query($sql, array $values = [], Projection $projection = null) 157 | { 158 | if ($projection === null) { 159 | $projection = $this->createProjection(); 160 | } 161 | 162 | $result = $this 163 | ->getSession() 164 | ->getClientUsingPooler('prepared_query', $sql) 165 | ->execute($values) 166 | ; 167 | 168 | $collection = new CollectionIterator( 169 | $result, 170 | $this->getSession(), 171 | $projection 172 | ); 173 | 174 | return $collection; 175 | } 176 | 177 | /** 178 | * createDefaultProjection 179 | * 180 | * This method creates a projection based on the structure definition of 181 | * the underlying relation. It may be used to shunt parent createProjection 182 | * call in inherited classes. 183 | * This method can be used where a projection that sticks to table 184 | * definition is needed like recursive CTEs. For normal projections, use 185 | * createProjection instead. 186 | * 187 | * @access public 188 | * @return Projection 189 | */ 190 | final public function createDefaultProjection() 191 | { 192 | return new Projection($this->flexible_entity_class, $this->structure->getDefinition()); 193 | } 194 | 195 | /** 196 | * createProjection 197 | * 198 | * This is a helper to create a new projection according to the current 199 | * structure.Overriding this method will change projection for all models. 200 | * 201 | * @access public 202 | * @return Projection 203 | */ 204 | public function createProjection() 205 | { 206 | return $this->createDefaultProjection(); 207 | } 208 | 209 | /** 210 | * checkFlexibleEntity 211 | * 212 | * Check if the given entity is an instance of this model's flexible class. 213 | * If not an exception is thrown. 214 | * 215 | * @access protected 216 | * @param FlexibleEntityInterface $entity 217 | * @throws \InvalidArgumentException 218 | * @return Model $this 219 | */ 220 | protected function checkFlexibleEntity(FlexibleEntityInterface $entity) 221 | { 222 | if (!($entity instanceof $this->flexible_entity_class)) { 223 | throw new \InvalidArgumentException(sprintf( 224 | "Entity class '%s' is not a '%s'.", 225 | get_class($entity), 226 | $this->flexible_entity_class 227 | )); 228 | } 229 | 230 | return $this; 231 | } 232 | 233 | /** 234 | * getStructure 235 | * 236 | * Return the structure. 237 | * 238 | * @access public 239 | * @return RowStructure 240 | */ 241 | public function getStructure() 242 | { 243 | return $this->structure; 244 | } 245 | 246 | /** 247 | * getFlexibleEntityClass 248 | * 249 | * Return the according flexible entity class associate with this Model 250 | * instance. 251 | * 252 | * @access public 253 | * @return string 254 | */ 255 | public function getFlexibleEntityClass() 256 | { 257 | return $this->flexible_entity_class; 258 | } 259 | 260 | /** 261 | * escapeLiteral 262 | * 263 | * Handy method to escape strings. 264 | * 265 | * @access protected 266 | * @param string $string 267 | * @return string 268 | */ 269 | protected function escapeLiteral($string) 270 | { 271 | return $this 272 | ->getSession() 273 | ->getConnection() 274 | ->escapeLiteral($string); 275 | } 276 | 277 | /** 278 | * escapeLiteral 279 | * 280 | * Handy method to escape strings. 281 | * 282 | * @access protected 283 | * @param string $string 284 | * @return string 285 | */ 286 | protected function escapeIdentifier($string) 287 | { 288 | return $this 289 | ->getSession() 290 | ->getConnection() 291 | ->escapeIdentifier($string); 292 | } 293 | 294 | /** 295 | * executeAnonymousQuery 296 | * 297 | * Handy method for DDL statements. 298 | * 299 | * @access protected 300 | * @param string $sql 301 | * @return Model $this 302 | */ 303 | protected function executeAnonymousQuery($sql) 304 | { 305 | $this 306 | ->getSession() 307 | ->getConnection() 308 | ->executeAnonymousQuery($sql); 309 | 310 | return $this; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /sources/lib/Model/ModelPooler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\Foundation\Client\ClientPooler; 13 | use PommProject\Foundation\Client\ClientPoolerInterface; 14 | use PommProject\ModelManager\Exception\ModelException; 15 | 16 | /** 17 | * ModelPooler 18 | * 19 | * Client pooler for model package. 20 | * 21 | * @package ModelManager 22 | * @copyright 2014 - 2015 Grégoire HUBERT 23 | * @author Grégoire HUBERT 24 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 25 | * @see ClientPooler 26 | */ 27 | class ModelPooler extends ClientPooler 28 | { 29 | /** 30 | * @see ClientPoolerInterface 31 | */ 32 | public function getPoolerType() 33 | { 34 | return 'model'; 35 | } 36 | 37 | /** 38 | * getClientFromPool 39 | * 40 | * @see ClientPooler 41 | * @return Model|null 42 | */ 43 | protected function getClientFromPool($class) 44 | { 45 | return $this->getSession()->getClient($this->getPoolerType(), trim($class, "\\")); 46 | } 47 | 48 | /** 49 | * createModel 50 | * 51 | * @see ClientPooler 52 | * @throws ModelException if incorrect 53 | * @return Model 54 | */ 55 | protected function createClient($class) 56 | { 57 | try { 58 | $reflection = new \ReflectionClass($class); 59 | } catch (\ReflectionException $e) { 60 | throw new ModelException(sprintf( 61 | "Could not instantiate Model class '%s'. (Reason: '%s').", 62 | $class, 63 | $e->getMessage() 64 | )); 65 | } 66 | 67 | if (!$reflection->implementsInterface('\PommProject\Foundation\Client\ClientInterface')) { 68 | throw new ModelException(sprintf("'%s' class does not implement the ClientInterface interface.", $class)); 69 | } 70 | 71 | if (!$reflection->isSubclassOf('\PommProject\ModelManager\Model\Model')) { 72 | throw new ModelException(sprintf("'%s' class does not extend \PommProject\ModelManager\Model.", $class)); 73 | } 74 | 75 | return new $class(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sources/lib/Model/ModelTrait/BaseTrait.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\ModelTrait; 11 | 12 | use PommProject\ModelManager\Model\CollectionIterator; 13 | use PommProject\ModelManager\Model\Projection; 14 | use PommProject\ModelManager\Model\RowStructure; 15 | use PommProject\ModelManager\Session; 16 | 17 | /** 18 | * BaseModelTrait 19 | * 20 | * Abstract methods for Model traits. 21 | * 22 | * @package ModelManager 23 | * @copyright 2014 - 2015 Grégoire HUBERT 24 | * @author Grégoire HUBERT 25 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 26 | */ 27 | trait BaseTrait 28 | { 29 | /** 30 | * @see Model 31 | * @return Projection 32 | */ 33 | abstract public function createProjection(); 34 | 35 | /** 36 | * @see Model 37 | * @return CollectionIterator 38 | */ 39 | abstract protected function query($sql, array $values = [], Projection $projection = null); 40 | 41 | /** 42 | * @see Model 43 | * @return Session 44 | */ 45 | abstract protected function getSession(); 46 | 47 | /** 48 | * @see Model 49 | * @return RowStructure 50 | 51 | */ 52 | abstract public function getStructure(); 53 | 54 | /** 55 | * @see Model 56 | */ 57 | abstract public function getFlexibleEntityClass(); 58 | 59 | /** 60 | * @see Model 61 | */ 62 | abstract public function escapeLiteral($string); 63 | 64 | /** 65 | * @see Model 66 | */ 67 | abstract public function escapeIdentifier($string); 68 | 69 | /** 70 | * @see Model 71 | */ 72 | abstract public function executeAnonymousQuery($sql); 73 | 74 | /** 75 | * @see Model 76 | */ 77 | abstract public function createEntity(array $values = []); 78 | } 79 | -------------------------------------------------------------------------------- /sources/lib/Model/ModelTrait/ReadQueries.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\ModelTrait; 11 | 12 | use PommProject\Foundation\Pager; 13 | use PommProject\Foundation\Where; 14 | use PommProject\ModelManager\Exception\ModelException; 15 | use PommProject\ModelManager\Model\CollectionIterator; 16 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 17 | use PommProject\ModelManager\Model\Projection; 18 | 19 | /** 20 | * ReadQueries 21 | * 22 | * Basic read queries for model instances. 23 | * 24 | * @package ModelManager 25 | * @copyright 2014 - 2015 Grégoire HUBERT 26 | * @author Grégoire HUBERT 27 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 28 | */ 29 | trait ReadQueries 30 | { 31 | use BaseTrait; 32 | 33 | /** 34 | * escapeIdentifier 35 | * 36 | * @see Model 37 | */ 38 | abstract protected function escapeIdentifier($string); 39 | 40 | /** 41 | * getStructure 42 | * 43 | * @see Model 44 | */ 45 | abstract public function getStructure(); 46 | 47 | /** 48 | * findAll 49 | * 50 | * Return all elements from a relation. If a suffix is given, it is append 51 | * to the query. This is mainly useful for "order by" statements. 52 | * NOTE: suffix is inserted as is with NO ESCAPING. DO NOT use it to place 53 | * "where" condition nor any untrusted params. 54 | * 55 | * @access public 56 | * @param string $suffix 57 | * @return CollectionIterator 58 | */ 59 | public function findAll($suffix = null) 60 | { 61 | $sql = strtr( 62 | "select :fields from :table :suffix", 63 | [ 64 | ':fields' => $this->createProjection()->formatFieldsWithFieldAlias(), 65 | ':table' => $this->getStructure()->getRelation(), 66 | ':suffix' => $suffix, 67 | ] 68 | ); 69 | 70 | return $this->query($sql); 71 | } 72 | 73 | /** 74 | * findWhere 75 | * 76 | * Perform a simple select on a given condition 77 | * NOTE: suffix is inserted as is with NO ESCAPING. DO NOT use it to place 78 | * "where" condition nor any untrusted params. 79 | * 80 | * @access public 81 | * @param mixed $where 82 | * @param array $values 83 | * @param string $suffix order by, limit, etc. 84 | * @return CollectionIterator 85 | */ 86 | public function findWhere($where, array $values = [], $suffix = '') 87 | { 88 | if (!$where instanceof Where) { 89 | $where = new Where($where, $values); 90 | } 91 | 92 | return $this->query($this->getFindWhereSql($where, $this->createProjection(), $suffix), $where->getValues()); 93 | } 94 | 95 | /** 96 | * findByPK 97 | * 98 | * Return an entity upon its primary key. If no entities are found, null is 99 | * returned. 100 | * 101 | * @access public 102 | * @param array $primary_key 103 | * @return FlexibleEntityInterface 104 | */ 105 | public function findByPK(array $primary_key) 106 | { 107 | $where = $this 108 | ->checkPrimaryKey($primary_key) 109 | ->getWhereFrom($primary_key) 110 | ; 111 | 112 | $iterator = $this->findWhere($where); 113 | 114 | return $iterator->isEmpty() ? null : $iterator->current(); 115 | } 116 | 117 | /** 118 | * countWhere 119 | * 120 | * Return the number of records matching a condition. 121 | * 122 | * @access public 123 | * @param string|Where $where 124 | * @param array $values 125 | * @return int 126 | */ 127 | public function countWhere($where, array $values = []) 128 | { 129 | $sql = sprintf( 130 | "select count(*) as result from %s where :condition", 131 | $this->getStructure()->getRelation() 132 | ); 133 | 134 | return $this->fetchSingleValue($sql, $where, $values); 135 | } 136 | 137 | /** 138 | * existWhere 139 | * 140 | * Check if rows matching the given condition do exist or not. 141 | * 142 | * @access public 143 | * @param mixed $where 144 | * @param array $values 145 | * @return bool 146 | */ 147 | public function existWhere($where, array $values = []) 148 | { 149 | $sql = sprintf( 150 | "select exists (select true from %s where :condition) as result", 151 | $this->getStructure()->getRelation() 152 | ); 153 | 154 | return $this->fetchSingleValue($sql, $where, $values); 155 | } 156 | 157 | /** 158 | * fetchSingleValue 159 | * 160 | * Fetch a single value named « result » from a query. 161 | * The query must be formatted with ":condition" as WHERE condition 162 | * placeholder. If the $where argument is a string, it is turned into a 163 | * Where instance. 164 | * 165 | * @access protected 166 | * @param string $sql 167 | * @param mixed $where 168 | * @param array $values 169 | * @return mixed 170 | */ 171 | protected function fetchSingleValue($sql, $where, array $values) 172 | { 173 | if (!$where instanceof Where) { 174 | $where = new Where($where, $values); 175 | } 176 | 177 | $sql = str_replace(":condition", (string) $where, $sql); 178 | 179 | return $this 180 | ->getSession() 181 | ->getClientUsingPooler('query_manager', '\PommProject\Foundation\PreparedQuery\PreparedQueryManager') 182 | ->query($sql, $where->getValues()) 183 | ->current()['result'] 184 | ; 185 | } 186 | 187 | /** 188 | * paginateFindWhere 189 | * 190 | * Paginate a query. 191 | * 192 | * @access public 193 | * @param Where $where 194 | * @param int $item_per_page 195 | * @param int $page 196 | * @param string $suffix 197 | * @return Pager 198 | */ 199 | public function paginateFindWhere(Where $where, $item_per_page, $page = 1, $suffix = '') 200 | { 201 | $projection = $this->createProjection(); 202 | 203 | return $this->paginateQuery( 204 | $this->getFindWhereSql($where, $projection, $suffix), 205 | $where->getValues(), 206 | $this->countWhere($where), 207 | $item_per_page, 208 | $page, 209 | $projection 210 | ); 211 | } 212 | 213 | /** 214 | * paginateQuery 215 | * 216 | * Paginate a SQL query. 217 | * It is important to note it adds limit and offset at the end of the given 218 | * query. 219 | * 220 | * @access protected 221 | * @param string $sql 222 | * @param array $values parameters 223 | * @param int $count 224 | * @param int $item_per_page 225 | * @param int $page 226 | * @param Projection $projection 227 | * @throws \InvalidArgumentException if pager args are invalid. 228 | * @return Pager 229 | */ 230 | protected function paginateQuery($sql, array $values, $count, $item_per_page, $page = 1, Projection $projection = null) 231 | { 232 | if ($page < 1) { 233 | throw new \InvalidArgumentException( 234 | sprintf("Page cannot be < 1. (%d given)", $page) 235 | ); 236 | } 237 | 238 | if ($item_per_page <= 0) { 239 | throw new \InvalidArgumentException( 240 | sprintf("'item_per_page' must be strictly positive (%d given).", $item_per_page) 241 | ); 242 | } 243 | 244 | $offset = $item_per_page * ($page - 1); 245 | $limit = $item_per_page; 246 | 247 | return new Pager( 248 | $this->query( 249 | sprintf("%s offset %d limit %d", $sql, $offset, $limit), 250 | $values, 251 | $projection 252 | ), 253 | $count, 254 | $item_per_page, 255 | $page 256 | ); 257 | } 258 | 259 | /** 260 | * getFindWhereSql 261 | * 262 | * This is the standard SQL query to fetch instances from the current 263 | * relation. 264 | * 265 | * @access protected 266 | * @param Where $where 267 | * @param Projection $projection 268 | * @param string $suffix 269 | * @return string 270 | */ 271 | protected function getFindWhereSql(Where $where, Projection $projection, $suffix = '') 272 | { 273 | return strtr( 274 | 'select :projection from :relation where :condition :suffix', 275 | [ 276 | ':projection' => $projection->formatFieldsWithFieldAlias(), 277 | ':relation' => $this->getStructure()->getRelation(), 278 | ':condition' => (string) $where, 279 | ':suffix' => $suffix, 280 | ] 281 | ); 282 | } 283 | 284 | /** 285 | * hasPrimaryKey 286 | * 287 | * Check if model has a primary key 288 | * 289 | * @access protected 290 | * @return bool 291 | */ 292 | protected function hasPrimaryKey() 293 | { 294 | $primaryKeys = $this->getStructure()->getPrimaryKey(); 295 | 296 | return !empty($primaryKeys); 297 | } 298 | 299 | /** 300 | * checkPrimaryKey 301 | * 302 | * Check if the given values fully describe a primary key. Throw a 303 | * ModelException if not. 304 | * 305 | * @access private 306 | * @param array $values 307 | * @throws ModelException 308 | * @return $this 309 | */ 310 | protected function checkPrimaryKey(array $values) 311 | { 312 | if (!$this->hasPrimaryKey()) { 313 | throw new ModelException( 314 | sprintf( 315 | "Attached structure '%s' has no primary key.", 316 | get_class($this->getStructure()) 317 | ) 318 | ); 319 | } 320 | 321 | foreach ($this->getStructure()->getPrimaryKey() as $key) { 322 | if (!isset($values[$key])) { 323 | throw new ModelException( 324 | sprintf( 325 | "Key '%s' is missing to fully describes the primary key {%s}.", 326 | $key, 327 | join(', ', $this->getStructure()->getPrimaryKey()) 328 | ) 329 | ); 330 | } 331 | } 332 | 333 | return $this; 334 | } 335 | 336 | /** 337 | * getWhereFrom 338 | * 339 | * Build a condition on given values. 340 | * 341 | * @access protected 342 | * @param array $values 343 | * @return Where 344 | */ 345 | protected function getWhereFrom(array $values) 346 | { 347 | $where = new Where(); 348 | 349 | foreach ($values as $field => $value) { 350 | $where->andWhere( 351 | sprintf( 352 | "%s = $*::%s", 353 | $this->escapeIdentifier($field), 354 | $this->getStructure()->getTypeFor($field) 355 | ), 356 | [$value] 357 | ); 358 | } 359 | 360 | return $where; 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /sources/lib/Model/ModelTrait/WriteQueries.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model\ModelTrait; 11 | 12 | use PommProject\Foundation\Where; 13 | use PommProject\ModelManager\Exception\ModelException; 14 | use PommProject\ModelManager\Model\CollectionIterator; 15 | use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface; 16 | use PommProject\ModelManager\Model\Model; 17 | 18 | /** 19 | * WriteQueries 20 | * 21 | * Basic write queries for model instances. 22 | * 23 | * @package ModelManager 24 | * @copyright 2014 - 2015 Grégoire HUBERT 25 | * @author Grégoire HUBERT 26 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 27 | */ 28 | trait WriteQueries 29 | { 30 | use ReadQueries; 31 | 32 | /** 33 | * insertOne 34 | * 35 | * Insert a new entity in the database. The entity is passed by reference. 36 | * It is updated with values returned by the database (ie, default values). 37 | * 38 | * @access public 39 | * @param FlexibleEntityInterface $entity 40 | * @return Model $this 41 | */ 42 | public function insertOne(FlexibleEntityInterface &$entity) 43 | { 44 | $values = $entity->fields( 45 | array_intersect( 46 | array_keys($this->getStructure()->getDefinition()), 47 | array_keys($entity->fields()) 48 | ) 49 | ); 50 | $sql = strtr( 51 | "insert into :relation (:fields) values (:values) returning :projection", 52 | [ 53 | ':relation' => $this->getStructure()->getRelation(), 54 | ':fields' => $this->getEscapedFieldList(array_keys($values)), 55 | ':projection' => $this->createProjection()->formatFieldsWithFieldAlias(), 56 | ':values' => join(',', $this->getParametersList($values)) 57 | ]); 58 | 59 | $entity = $this 60 | ->query($sql, array_values($values)) 61 | ->current() 62 | ->status(FlexibleEntityInterface::STATUS_EXIST); 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * updateOne 69 | * 70 | * Update the entity. ONLY the fields indicated in the $fields array are 71 | * updated. The entity is passed by reference and its values are updated 72 | * with the values from the database. This means all changes not updated 73 | * are lost. The update is made upon a condition on the primary key. If the 74 | * primary key is not fully set, an exception is thrown. 75 | * 76 | * @access public 77 | * @param FlexibleEntityInterface $entity 78 | * @param array $fields 79 | * @return Model $this 80 | */ 81 | public function updateOne(FlexibleEntityInterface &$entity, array $fields) 82 | { 83 | $entity = $this->updateByPk( 84 | $entity->fields($this->getStructure()->getPrimaryKey()), 85 | $entity->fields($fields) 86 | ); 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * updateByPk 93 | * 94 | * Update a record and fetch it with its new values. If no records match 95 | * the given key, null is returned. 96 | * 97 | * @access public 98 | * @param array $primary_key 99 | * @param array $updates 100 | * @throws ModelException 101 | * @return FlexibleEntityInterface 102 | */ 103 | public function updateByPk(array $primary_key, array $updates) 104 | { 105 | $where = $this 106 | ->checkPrimaryKey($primary_key) 107 | ->getWhereFrom($primary_key) 108 | ; 109 | $parameters = $this->getParametersList($updates); 110 | $update_strings = []; 111 | 112 | foreach ($updates as $field_name => $new_value) { 113 | $update_strings[] = sprintf( 114 | "%s = %s", 115 | $this->escapeIdentifier($field_name), 116 | $parameters[$field_name] 117 | ); 118 | } 119 | 120 | $sql = strtr( 121 | "update :relation set :update where :condition returning :projection", 122 | [ 123 | ':relation' => $this->getStructure()->getRelation(), 124 | ':update' => join(', ', $update_strings), 125 | ':condition' => (string) $where, 126 | ':projection' => $this->createProjection()->formatFieldsWithFieldAlias(), 127 | ] 128 | ); 129 | 130 | $iterator = $this->query($sql, array_merge(array_values($updates), $where->getValues())); 131 | 132 | if ($iterator->isEmpty()) { 133 | return null; 134 | } 135 | 136 | return $iterator->current()->status(FlexibleEntityInterface::STATUS_EXIST); 137 | } 138 | 139 | /** 140 | * deleteOne 141 | * 142 | * Delete an entity from a table. Entity is passed by reference and is 143 | * updated with the values fetched from the deleted record. 144 | * 145 | * @access public 146 | * @param FlexibleEntityInterface $entity 147 | * @return Model $this 148 | */ 149 | public function deleteOne(FlexibleEntityInterface &$entity) 150 | { 151 | $entity = $this->deleteByPK($entity->fields($this->getStructure()->getPrimaryKey())); 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * deleteByPK 158 | * 159 | * Delete a record from its primary key. The deleted entity is returned or 160 | * null if not found. 161 | * 162 | * @access public 163 | * @param array $primary_key 164 | * @throws ModelException 165 | * @return FlexibleEntityInterface 166 | */ 167 | public function deleteByPK(array $primary_key) 168 | { 169 | $where = $this 170 | ->checkPrimaryKey($primary_key) 171 | ->getWhereFrom($primary_key) 172 | ; 173 | 174 | return $this->deleteWhere($where)->current(); 175 | } 176 | 177 | /** 178 | * deleteWhere 179 | * 180 | * Delete records by a given condition. A collection of all deleted entries is returned. 181 | * 182 | * @param $where 183 | * @param array $values 184 | * @return CollectionIterator 185 | */ 186 | public function deleteWhere($where, array $values = []) 187 | { 188 | if (!$where instanceof Where) { 189 | $where = new Where($where, $values); 190 | } 191 | 192 | $sql = strtr( 193 | "delete from :relation where :condition returning :projection", 194 | [ 195 | ':relation' => $this->getStructure()->getRelation(), 196 | ':condition' => (string) $where, 197 | ':projection' => $this->createProjection()->formatFieldsWithFieldAlias(), 198 | ] 199 | ); 200 | 201 | $collection = $this->query($sql, $where->getValues()); 202 | foreach ($collection as $entity) { 203 | $entity->status(FlexibleEntityInterface::STATUS_NONE); 204 | } 205 | $collection->rewind(); 206 | 207 | return $collection; 208 | } 209 | 210 | /** 211 | * createAndSave 212 | * 213 | * Create a new entity from given values and save it in the database. 214 | * 215 | * @access public 216 | * @param array $values 217 | * @return FlexibleEntityInterface 218 | */ 219 | public function createAndSave(array $values) 220 | { 221 | $entity = $this->createEntity($values); 222 | $this->insertOne($entity); 223 | 224 | return $entity; 225 | } 226 | 227 | /** 228 | * getEscapedFieldList 229 | * 230 | * Return a comma separated list with the given escaped field names. 231 | * 232 | * @access protected 233 | * @param array $fields 234 | * @return string 235 | */ 236 | public function getEscapedFieldList(array $fields) 237 | { 238 | return join( 239 | ', ', 240 | array_map( 241 | function ($field) { return $this->escapeIdentifier($field); }, 242 | $fields 243 | )); 244 | } 245 | 246 | /** 247 | * getParametersList 248 | * 249 | * Create a parameters list from values. 250 | * 251 | * @access protected 252 | * @param array $values 253 | * @return array $escape codes 254 | */ 255 | protected function getParametersList(array $values) 256 | { 257 | $parameters = []; 258 | 259 | foreach ($values as $name => $value) { 260 | $parameters[$name] = sprintf( 261 | "$*::%s", 262 | $this->getStructure()->getTypeFor($name) 263 | ); 264 | } 265 | 266 | return $parameters; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /sources/lib/Model/Projection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\ModelManager\Exception\ModelException; 13 | 14 | /** 15 | * Projection 16 | * 17 | * Define the content of SELECT or RETURNING (projection) statements. 18 | * 19 | * @package ModelManager 20 | * @copyright 2014 - 2015 Grégoire HUBERT 21 | * @author Grégoire HUBERT 22 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 23 | */ 24 | class Projection implements \IteratorAggregate 25 | { 26 | protected $flexible_entity_class; 27 | protected $fields = []; 28 | protected $types = []; 29 | 30 | 31 | /** 32 | * __construct 33 | * 34 | * @access public 35 | * @param $flexible_entity_class 36 | * @param array $structure list of field names with types. 37 | */ 38 | public function __construct($flexible_entity_class, array $structure = null) 39 | { 40 | $this->flexible_entity_class = $flexible_entity_class; 41 | 42 | if ($structure != null) { 43 | foreach ($structure as $field_name => $type) { 44 | $this->setField($field_name, sprintf("%%:%s:%%", $field_name), $type); 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * getIterator 51 | * 52 | * This returns an ArrayIterator using the name => type association of the 53 | * projection. 54 | * @see IteratorAggregate 55 | * 56 | * @access public 57 | * @return \ArrayIterator 58 | */ 59 | public function getIterator() 60 | { 61 | return new \ArrayIterator($this->types); 62 | } 63 | 64 | /** 65 | * getFlexibleEntityClass 66 | * 67 | * Get the flexible entity class associated with this projection. 68 | * 69 | * @access public 70 | * @return string 71 | */ 72 | public function getFlexibleEntityClass() 73 | { 74 | return $this->flexible_entity_class; 75 | } 76 | 77 | /** 78 | * setField 79 | * 80 | * Set a field with a content. This override previous definition if exist. 81 | * 82 | * @access public 83 | * @param string $name 84 | * @param string $content 85 | * @param string $type (null) 86 | * @throws \InvalidArgumentException if $name or $content is null 87 | * @return Projection $this 88 | */ 89 | public function setField($name, $content, $type = null) 90 | { 91 | if ($content === null) { 92 | throw new \InvalidArgumentException(sprintf("Content cannot be null for field '%s'.", $name)); 93 | } 94 | 95 | $this->checkField($name)->fields[$name] = $content; 96 | $this->types[$name] = $type; 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * setFieldType 103 | * 104 | * Set or override a field type definition. 105 | * 106 | * @access public 107 | * @param string $name 108 | * @param string $type 109 | * @throws ModelException if name is null or does not exist. 110 | * @return Projection $this 111 | */ 112 | public function setFieldType($name, $type) 113 | { 114 | $this->checkFieldExist($name)->types[$name] = $type; 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * unsetField 121 | * 122 | * Unset an existing field 123 | * 124 | * @access public 125 | * @param string $name 126 | * @throws ModelException if field $name does not exist. 127 | * @return Projection $this 128 | */ 129 | public function unsetField($name) 130 | { 131 | $this->checkFieldExist($name); 132 | unset($this->fields[$name], $this->types[$name]); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * hasField 139 | * 140 | * Return if the given field exist. 141 | * 142 | * @access public 143 | * @param string $name 144 | * @return boolean 145 | */ 146 | public function hasField($name) 147 | { 148 | return isset($this->checkField($name)->fields[$name]); 149 | } 150 | 151 | /** 152 | * getFieldType 153 | * 154 | * Return the type associated with the given field. 155 | * 156 | * @access public 157 | * @param string $name 158 | * @throws ModelException if $name is null or field does not exist 159 | * @return string null if type is not set 160 | */ 161 | public function getFieldType($name) 162 | { 163 | return $this->checkFieldExist($name)->types[$name] != null 164 | ? rtrim($this->types[$name], '[]') 165 | : null; 166 | } 167 | 168 | /** 169 | * isArray 170 | * 171 | * Tel if a field is an array. 172 | * 173 | * @access public 174 | * @param string $name 175 | * @throws ModelException if $name does not exist. 176 | * @throws \InvalidArgumentException if $name is null 177 | * @return bool 178 | */ 179 | public function isArray($name) 180 | { 181 | return (bool) preg_match('/\[\]$/', $this->checkFieldExist($name)->types[$name]); 182 | } 183 | 184 | /** 185 | * getFieldNames 186 | * 187 | * Return fields names list. 188 | * 189 | * @access public 190 | * @return array fields list 191 | */ 192 | public function getFieldNames() 193 | { 194 | return array_keys($this->fields); 195 | } 196 | 197 | /** 198 | * getFieldTypes 199 | * 200 | * Return an array with the known types. 201 | * 202 | * @access public 203 | * @return array 204 | */ 205 | public function getFieldTypes() 206 | { 207 | $fields = []; 208 | foreach ($this->fields as $name => $value) { 209 | $fields[$name] = isset($this->types[$name]) 210 | ? $this->types[$name] 211 | : null 212 | ; 213 | } 214 | 215 | return $fields; 216 | } 217 | 218 | /** 219 | * getFieldWithTableAlias 220 | * 221 | * Prepend the field name with alias if given. 222 | * 223 | * @access public 224 | * @param string $name 225 | * @param string $table_alias 226 | * @throws ModelException if $name does not exist. 227 | * @throws \InvalidArgumentException if $name is null 228 | * @return string 229 | */ 230 | public function getFieldWithTableAlias($name, $table_alias = null) 231 | { 232 | $replace = $table_alias === null ? '' : sprintf("%s.", $table_alias); 233 | 234 | return $this->replaceToken($this->checkFieldExist($name)->fields[$name], $replace); 235 | } 236 | 237 | /** 238 | * getFieldsWithTableAlias 239 | * 240 | * Return the array of fields with table aliases expanded. 241 | * 242 | * @access public 243 | * @param string $table_alias (null) 244 | * @return array 245 | */ 246 | public function getFieldsWithTableAlias($table_alias = null) 247 | { 248 | $vals = []; 249 | $replace = $table_alias === null ? '' : sprintf("%s.", $table_alias); 250 | 251 | foreach ($this->fields as $name => $definition) { 252 | $vals[$name] = $this->replaceToken($this->fields[$name], $replace); 253 | } 254 | 255 | return $vals; 256 | } 257 | 258 | /** 259 | * formatFields 260 | * 261 | * Return a formatted string with fields like 262 | * a.field1, a.field2, ..., a.fieldN 263 | * 264 | * @access public 265 | * @param string $table_alias 266 | * @return string 267 | */ 268 | public function formatFields($table_alias = null) 269 | { 270 | return join(', ', $this->getFieldsWithTableAlias($table_alias)); 271 | } 272 | 273 | /** 274 | * formatFieldsWithFieldAlias 275 | * 276 | * Return a formatted string with fields like 277 | * a.field1 AS field1, a.field2 AS fields2, ... 278 | * 279 | * @access public 280 | * @param string $table_alias 281 | * @return string 282 | */ 283 | public function formatFieldsWithFieldAlias($table_alias = null) 284 | { 285 | $fields = $this->getFieldsWithTableAlias($table_alias); 286 | 287 | return join( 288 | ', ', 289 | array_map( 290 | function ($field_alias, $field_definition) { 291 | return sprintf( 292 | '%s as "%s"', 293 | $field_definition, 294 | addcslashes($field_alias, '"\\') 295 | ); 296 | }, 297 | array_keys($fields), 298 | $fields 299 | ) 300 | ); 301 | } 302 | 303 | /** 304 | * __toString 305 | * 306 | * String representation = formatFieldsWithFieldAlias(). 307 | * 308 | * @access public 309 | * @return string 310 | */ 311 | public function __toString() 312 | { 313 | return $this->formatFieldsWithFieldAlias(); 314 | } 315 | 316 | /** 317 | * checkField 318 | * 319 | * Check if $name is not null 320 | * 321 | * @access private 322 | * @param string $name 323 | * @throws \InvalidArgumentException if name is null 324 | * @return Projection $this 325 | */ 326 | private function checkField($name) 327 | { 328 | if ($name === null) { 329 | throw new \InvalidArgumentException(sprintf("Field name cannot be null.")); 330 | } 331 | 332 | return $this; 333 | } 334 | 335 | /** 336 | * checkFieldExist 337 | * 338 | * Check if a field exist. 339 | * 340 | * @access private 341 | * @param string $name 342 | * @throws ModelException if field does not exist 343 | * @return Projection $this 344 | */ 345 | private function checkFieldExist($name) 346 | { 347 | if (!$this->checkField($name)->hasField($name)) { 348 | throw new ModelException(sprintf("Field '%s' does not exist. Available fields are {%s}.", $name, join(', ', $this->getFieldNames()))); 349 | } 350 | 351 | return $this; 352 | } 353 | 354 | /** 355 | * replaceToken 356 | * 357 | * Replace placeholders with their quoted names. 358 | * 359 | * @access protected 360 | * @param string $string field definition 361 | * @param string $prefix optional unquoted prefix 362 | * @return string 363 | */ 364 | protected function replaceToken($string, $prefix = '') 365 | { 366 | return preg_replace_callback( 367 | '/%:(\w.*):%/U', 368 | function (array $matches) use ($prefix) { 369 | return sprintf('%s"%s"', $prefix, addcslashes($matches[1], '"\\')); 370 | }, 371 | $string 372 | ); 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /sources/lib/Model/RowStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Model; 11 | 12 | use PommProject\ModelManager\Exception\ModelException; 13 | 14 | /** 15 | * RowStructure 16 | * 17 | * Represent a composite structure like table or row. 18 | * 19 | * @package ModelManager 20 | * @copyright 2014 - 2015 Grégoire HUBERT 21 | * @author Grégoire HUBERT 22 | * @license MIT/X11 {@link http://opensource.org/licenses/mit-license.php} 23 | */ 24 | class RowStructure implements \ArrayAccess 25 | { 26 | protected $primary_key = []; 27 | protected $field_definitions = []; 28 | protected $relation; 29 | 30 | /** 31 | * setDefinition 32 | * 33 | * Add a complete definition. 34 | * 35 | * @access public 36 | * @param array $definition 37 | * @return RowStructure $this 38 | */ 39 | public function setDefinition(array $definition) 40 | { 41 | $this->field_definitions = $definition; 42 | 43 | return $this; 44 | } 45 | 46 | /** 47 | * inherits 48 | * 49 | * Add inherited structure. 50 | * 51 | * @access public 52 | * @param RowStructure $structure 53 | * @return RowStructure $this 54 | */ 55 | public function inherits(RowStructure $structure) 56 | { 57 | foreach ($structure->getDefinition() as $field => $type) { 58 | $this->addField($field, $type); 59 | } 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * setRelation 66 | * 67 | * Set or change the relation. 68 | * 69 | * @access public 70 | * @param string $relation 71 | * @return RowStructure $this 72 | */ 73 | public function setRelation($relation) 74 | { 75 | $this->relation = $relation; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * setPrimaryKey 82 | * 83 | * Set or change the primary key definition. 84 | * 85 | * @access public 86 | * @param array $primary_key 87 | * @return RowStructure $this 88 | */ 89 | public function setPrimaryKey(array $primary_key) 90 | { 91 | $this->primary_key = $primary_key; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * addField 98 | * 99 | * Add a new field structure. 100 | * 101 | * @access public 102 | * @param string $name 103 | * @param string $type 104 | * @throws ModelException if type or name is null 105 | * @return RowStructure $this 106 | */ 107 | public function addField($name, $type) 108 | { 109 | $this->checkNotNull($type, 'type') 110 | ->checkNotNull($name, 'name') 111 | ->field_definitions[$name] = $type; 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * getFieldNames 118 | * 119 | * Return an array of all field names 120 | * 121 | * @access public 122 | * @return array 123 | */ 124 | public function getFieldNames() 125 | { 126 | return array_keys($this->field_definitions); 127 | } 128 | 129 | /** 130 | * hasField 131 | * 132 | * Check if a field exist in the structure 133 | * 134 | * @access public 135 | * @param string $name 136 | * @throws ModelException if $name is null 137 | * @return bool 138 | */ 139 | public function hasField($name) 140 | { 141 | return array_key_exists($name, $this->checkNotNull($name, 'name')->field_definitions); 142 | } 143 | 144 | /** 145 | * getTypeFor 146 | * 147 | * Return the type associated with the field 148 | * 149 | * @access public 150 | * @param string $name 151 | * @throws ModelException if $name is null or name does not exist. 152 | * @return string $type 153 | */ 154 | public function getTypeFor($name) 155 | { 156 | return $this->checkExist($name)->field_definitions[$name]; 157 | } 158 | 159 | /** 160 | * getDefinition 161 | * 162 | * Return all fields and types 163 | * 164 | * @return array 165 | */ 166 | public function getDefinition() 167 | { 168 | return $this->field_definitions; 169 | } 170 | 171 | /** 172 | * getRelation 173 | * 174 | * Return the relation name. 175 | * 176 | * @access public 177 | * @return string 178 | */ 179 | public function getRelation() 180 | { 181 | return $this->relation; 182 | } 183 | 184 | /** 185 | * getPrimaryKey 186 | * 187 | * Return the primary key definition. 188 | * 189 | * @access public 190 | * @return array 191 | */ 192 | public function getPrimaryKey() 193 | { 194 | return $this->primary_key; 195 | } 196 | 197 | /** 198 | * checkNotNull 199 | * 200 | * Test if given value is null. 201 | * 202 | * @access private 203 | * @param string $val 204 | * @param string $name 205 | * @throws \InvalidArgumentException if $val is null 206 | * @return RowStructure $this 207 | */ 208 | private function checkNotNull($val, $name) 209 | { 210 | if ($val === null) { 211 | throw new \InvalidArgumentException(sprintf("'%s' cannot be null in '%s'.", $name, get_class($this))); 212 | } 213 | 214 | return $this; 215 | } 216 | 217 | /** 218 | * checkExist 219 | * 220 | * Test if a field exist. 221 | * 222 | * @access private 223 | * @param string $name 224 | * @throws ModelException if $name does not exist. 225 | * @return RowStructure $this 226 | */ 227 | private function checkExist($name) 228 | { 229 | if (!$this->hasField($name)) { 230 | throw new ModelException( 231 | sprintf( 232 | "Field '%s' is not defined in structure '%s'. Defined fields are {%s}", 233 | $name, 234 | get_class($this), 235 | join(', ', array_keys($this->field_definitions)) 236 | ) 237 | ); 238 | } 239 | 240 | return $this; 241 | } 242 | 243 | /** 244 | * @see \ArrayAccess 245 | */ 246 | public function offsetSet($name, $type) 247 | { 248 | $this->addField($name, $type); 249 | } 250 | 251 | /** 252 | * @see \ArrayAccess 253 | */ 254 | public function offsetGet($name) 255 | { 256 | return $this->getTypeFor($name); 257 | } 258 | 259 | /** 260 | * @see \ArrayAccess 261 | */ 262 | public function offsetExists($name) 263 | { 264 | return $this->hasField($name); 265 | } 266 | 267 | /** 268 | * @see \ArrayAccess 269 | */ 270 | public function offsetUnset($name) 271 | { 272 | throw new ModelException(sprintf("Cannot unset a structure field ('%s').", $name)); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /sources/lib/ModelLayer/ModelLayerPooler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\ModelLayer; 11 | 12 | use PommProject\Foundation\Client\ClientPooler; 13 | use PommProject\Foundation\Client\ClientPoolerInterface; 14 | use PommProject\ModelManager\Exception\ModelLayerException; 15 | 16 | /** 17 | * ModelLayerPooler 18 | * 19 | * Pooler for ModelLayer session client. 20 | * 21 | * @package ModelManager 22 | * @copyright 2014 - 2015 Grégoire HUBERT 23 | * @author Grégoire HUBERT 24 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 25 | * @see ClientPooler 26 | */ 27 | class ModelLayerPooler extends ClientPooler 28 | { 29 | /** 30 | * getPoolerType 31 | * 32 | * @see ClientPoolerInterface 33 | */ 34 | public function getPoolerType() 35 | { 36 | return 'model_layer'; 37 | } 38 | 39 | /** 40 | * createClient 41 | * 42 | * @see ClientPooler 43 | * @return ModelLayer 44 | * @throws ModelLayerException 45 | */ 46 | protected function createClient($identifier) 47 | { 48 | try { 49 | $reflection = new \ReflectionClass($identifier); 50 | if (!$reflection->isSubclassOf('\PommProject\ModelManager\ModelLayer\ModelLayer')) { 51 | throw new ModelLayerException(sprintf("Class '%s' is not a subclass of ModelLayer.", $identifier)); 52 | } 53 | } catch (\ReflectionException $e) { 54 | throw new ModelLayerException(sprintf("Error while loading class '%s' (%s).", $identifier, $e->getMessage())); 55 | } 56 | 57 | return new $identifier(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sources/lib/Session.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager; 11 | 12 | use PommProject\Foundation\Session as FoundationSession; 13 | use PommProject\ModelManager\Model\Model; 14 | use PommProject\ModelManager\ModelLayer\ModelLayer; 15 | 16 | /** 17 | * Session 18 | * 19 | * Model manager's session. 20 | * It adds proxy method to use model manager's poolers. 21 | * 22 | * @package ModelManager 23 | * @copyright 2015 Grégoire HUBERT 24 | * @author Grégoire HUBERT 25 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 26 | * 27 | * @see FoundationSession 28 | */ 29 | class Session extends FoundationSession 30 | { 31 | /** 32 | * getModel 33 | * 34 | * Return a model instance 35 | * 36 | * @access public 37 | * @param string $class 38 | * @return Model 39 | */ 40 | public function getModel($class) 41 | { 42 | return $this->getClientUsingPooler('model', $class); 43 | } 44 | 45 | /** 46 | * getModelLayer 47 | * 48 | * Return a model layer instance 49 | * 50 | * @access public 51 | * @param string $class 52 | * @return ModelLayer 53 | */ 54 | public function getModelLayer($class) 55 | { 56 | return $this->getClientUsingPooler('model_layer', $class); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sources/lib/SessionBuilder.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager; 11 | 12 | use PommProject\Foundation\Client\ClientHolder; 13 | use PommProject\Foundation\Session as FoundationSession; 14 | use PommProject\Foundation\Session\Connection; 15 | use PommProject\Foundation\Session\Session; 16 | use PommProject\Foundation\SessionBuilder as FoundationSessionBuilder; 17 | use PommProject\ModelManager\Model\ModelPooler; 18 | use PommProject\ModelManager\ModelLayer\ModelLayerPooler; 19 | use PommProject\ModelManager\Session as ModelManagerSession; 20 | 21 | /** 22 | * SessionBuilder 23 | * 24 | * Session builder for the ModelManager package. 25 | * 26 | * @package ModelManager 27 | * @copyright 2014 - 2015 Grégoire HUBERT 28 | * @author Grégoire HUBERT 29 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 30 | * @see FoundationSessionBuilder 31 | */ 32 | class SessionBuilder extends FoundationSessionBuilder 33 | { 34 | /** 35 | * postConfigure 36 | * 37 | * Register ModelManager's poolers. 38 | * 39 | * @access protected 40 | * @param Session $session 41 | * @return SessionBuilder 42 | */ 43 | protected function postConfigure(Session $session) 44 | { 45 | parent::postConfigure($session); 46 | $session 47 | ->registerClientPooler(new ModelPooler) 48 | ->registerClientPooler(new ModelLayerPooler) 49 | ; 50 | 51 | return $this; 52 | } 53 | 54 | 55 | /** 56 | * createSession 57 | * 58 | * @param Connection $connection 59 | * @param ClientHolder $client_holder 60 | * @param null|string $stamp 61 | * @return ModelManagerSession 62 | * @see VanillaSessionBuilder 63 | */ 64 | protected function createSession(Connection $connection, ClientHolder $client_holder, $stamp) 65 | { 66 | $this->configuration->setDefaultValue('class:session', '\PommProject\ModelManager\Session'); 67 | 68 | return parent::createSession($connection, $client_holder, $stamp); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sources/lib/Tester/ModelSessionAtoum.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Tester; 11 | 12 | use PommProject\Foundation\Tester\FoundationSessionAtoum; 13 | use PommProject\ModelManager\SessionBuilder; 14 | 15 | /** 16 | * ModelSessionAwareAtoum 17 | * 18 | * Session aware Atoum instance. This uses ModelManager's session builder to 19 | * ensure all poolers are loaded. 20 | * 21 | * @package ModelManager 22 | * @copyright 2014 - 2015 Grégoire HUBERT 23 | * @author Grégoire HUBERT 24 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 25 | * @see FoundationSessionAtoum 26 | */ 27 | abstract class ModelSessionAtoum extends FoundationSessionAtoum 28 | { 29 | protected function createSessionBuilder(array $configuration) 30 | { 31 | return new SessionBuilder($configuration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ComplexFixture.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity; 13 | 14 | class ComplexFixture extends FlexibleEntity 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ComplexFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\ModelManager\Model\Model; 14 | use PommProject\ModelManager\Model\ModelTrait\WriteQueries; 15 | 16 | class ComplexFixtureModel extends Model 17 | { 18 | use WriteQueries; 19 | 20 | public function __construct() 21 | { 22 | $this->structure = new ComplexFixtureStructure(); 23 | $this->flexible_entity_class = 'PommProject\ModelManager\Test\Fixture\ComplexFixture'; 24 | } 25 | 26 | public function initialize(Session $session) 27 | { 28 | parent::initialize($session); 29 | $this->dropTable(); 30 | $this->createTable(); 31 | } 32 | 33 | public function shutdown() 34 | { 35 | $this->dropTable(); 36 | } 37 | 38 | protected function createTable() 39 | { 40 | $sql = <<executeAnonymousQuery(sprintf($sql, $this->getStructure()->getRelation())); 52 | 53 | $sql = <<executeAnonymousQuery(sprintf($sql, $this->getStructure()->getRelation())); 71 | 72 | return $this; 73 | } 74 | 75 | protected function dropTable() 76 | { 77 | $this 78 | ->executeAnonymousQuery( 79 | sprintf( 80 | "drop table if exists %s", 81 | $this->getStructure()->getRelation() 82 | ) 83 | ) 84 | ; 85 | 86 | return $this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ComplexFixtureStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\RowStructure; 13 | 14 | class ComplexFixtureStructure extends RowStructure 15 | { 16 | public function __construct() 17 | { 18 | $this 19 | ->setRelation('complex_fixture') 20 | ->setPrimaryKey(['id', 'version_id']) 21 | ->addField('id', 'int4') 22 | ->addField('version_id', 'int4') 23 | ->addField('complex_number', 'pomm_test.complex_number') 24 | ->addField('complex_numbers', 'pomm_test.complex_number[]') 25 | ->addField('created_at', 'timestamptz') 26 | ->addField('updated_at', 'timestamptz[]') 27 | ; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ComplexNumber.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity; 13 | 14 | class ComplexNumber extends FlexibleEntity 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ComplexNumberStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\RowStructure; 13 | 14 | class ComplexNumberStructure extends RowStructure 15 | { 16 | public function __construct() 17 | { 18 | $this 19 | ->setRelation('pomm_test.complex_number') 20 | ->addField('real', 'float8') 21 | ->addField('imaginary', 'float8') 22 | ; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ModelSchemaClient.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Client\Client; 13 | use PommProject\Foundation\Exception\SqlException; 14 | use PommProject\Foundation\Session\Session; 15 | 16 | class ModelSchemaClient extends Client 17 | { 18 | public function getClientType() 19 | { 20 | return 'test'; 21 | } 22 | 23 | public function getClientIdentifier() 24 | { 25 | return 'complex_fixture'; 26 | } 27 | 28 | public function initialize(Session $session) 29 | { 30 | parent::initialize($session); 31 | 32 | $this->createSchema(); 33 | } 34 | 35 | public function shutdown() 36 | { 37 | $this->dropSchema(); 38 | } 39 | 40 | public function createSchema() 41 | { 42 | $sql = 43 | [ 44 | "drop schema if exists pomm_test cascade", 45 | "begin", 46 | "create schema pomm_test", 47 | "create type pomm_test.complex_number as (real float8, imaginary float8)", 48 | "commit", 49 | ]; 50 | 51 | try { 52 | foreach ($sql as $stmt) { 53 | $this->executeSql($stmt); 54 | } 55 | } catch (SqlException $e) { 56 | $this->executeSql('rollback'); 57 | throw $e; 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | public function dropSchema() 64 | { 65 | $sql = "drop schema if exists pomm_test cascade"; 66 | $this->executeSql($sql); 67 | 68 | return $this; 69 | } 70 | 71 | protected function executeSql($sql) 72 | { 73 | return $this 74 | ->getSession() 75 | ->getConnection() 76 | ->executeAnonymousQuery($sql) 77 | ; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sources/tests/Fixture/ReadFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\ModelTrait\ReadQueries; 13 | 14 | class ReadFixtureModel extends SimpleFixtureModel 15 | { 16 | use ReadQueries; 17 | } 18 | -------------------------------------------------------------------------------- /sources/tests/Fixture/SimpleFixture.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity; 13 | use PommProject\Foundation\Inflector; 14 | 15 | class SimpleFixture extends FlexibleEntity 16 | { 17 | public function extract() 18 | { 19 | $fields = parent::extract(); 20 | $new_fiels = []; 21 | 22 | foreach ($fields as $name => $value) { 23 | $new_fiels[Inflector::studlyCaps($name)] = $value; 24 | } 25 | 26 | return $new_fiels; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sources/tests/Fixture/SimpleFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Where; 13 | use PommProject\ModelManager\Model\Model; 14 | 15 | class SimpleFixtureModel extends Model 16 | { 17 | public function __construct() 18 | { 19 | $this->structure = new SimpleFixtureStructure(); 20 | $this->flexible_entity_class = '\PommProject\ModelManager\Test\Fixture\SimpleFixture'; 21 | } 22 | 23 | public function doSimpleQuery(Where $where = null) 24 | { 25 | if ($where === null) { 26 | $where = new Where(); 27 | } 28 | 29 | $sql = strtr( 30 | "select :fields from :relation where :condition", 31 | [ 32 | ':fields' => $this->createProjection()->formatFieldsWithFieldAlias(), 33 | ':relation' => $this->getStructure()->getRelation(), 34 | ':condition' => (string) $where, 35 | ] 36 | ); 37 | 38 | return $this->query($sql, $where->getValues()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sources/tests/Fixture/SimpleFixtureStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\RowStructure; 13 | 14 | class SimpleFixtureStructure extends RowStructure 15 | { 16 | public function __construct() 17 | { 18 | $this->relation = <<<_ 19 | (values 20 | (1::int4,'one'::varchar, bool 't'), 21 | (2,'two', 'f'), 22 | (3,'three', 'f'), 23 | (4,'four', 't') 24 | ) 25 | simple_fixture (id, a_varchar, a_boolean) 26 | _; 27 | $this 28 | ->addField('id', 'int4') 29 | ->addField('a_varchar', 'varchar') 30 | ->addField('a_boolean', 'bool') 31 | ->primary_key = ['id'] 32 | ; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sources/tests/Fixture/SimpleModelLayer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\ModelLayer\ModelLayer; 13 | 14 | /** 15 | * SimpleModelLayer 16 | * 17 | * This class is NOT the right example of how ModelLayer is to be used. Good 18 | * practices are to handle complete transaction within a single method. 19 | * Transactions are split in several methods here to be tested properly. 20 | * 21 | * @package Pomm 22 | * @copyright 2014 Grégoire HUBERT 23 | * @author Grégoire HUBERT 24 | * @license X11 {@link http://opensource.org/licenses/mit-license.php} 25 | * @see ModelLayer 26 | */ 27 | class SimpleModelLayer extends ModelLayer 28 | { 29 | public function startTransaction() 30 | { 31 | return parent::startTransaction() 32 | ->isInTransaction() 33 | ; 34 | } 35 | 36 | public function rollbackTransaction($name = null) 37 | { 38 | return parent::rollbackTransaction($name) 39 | ->isInTransaction() 40 | ; 41 | } 42 | 43 | public function setSavepoint($name) 44 | { 45 | return parent::setSavepoint($name) 46 | ->isInTransaction() 47 | ; 48 | } 49 | 50 | public function releaseSavepoint($name) 51 | { 52 | return parent::releaseSavepoint($name) 53 | ->isInTransaction() 54 | ; 55 | } 56 | 57 | public function commitTransaction() 58 | { 59 | return parent::commitTransaction(); 60 | } 61 | 62 | public function sendNotify($channel, $data = '') 63 | { 64 | $observer = $this 65 | ->getSession() 66 | ->getObserver($channel) 67 | ->RestartListening() 68 | ; 69 | parent::sendNotify($channel, $data); 70 | sleep(0.3); 71 | 72 | return $observer 73 | ->getNotification() 74 | ; 75 | } 76 | 77 | public function isInTransaction() 78 | { 79 | return parent::isInTransaction(); 80 | } 81 | 82 | public function isTransactionOk() 83 | { 84 | return parent::isTransactionOk(); 85 | } 86 | 87 | public function setDeferrable(array $keys, $state) 88 | { 89 | return parent::setDeferrable($keys, $state); 90 | } 91 | 92 | public function setTransactionIsolationLevel($level) 93 | { 94 | return parent::setTransactionIsolationLevel($level); 95 | } 96 | 97 | public function setTransactionAccessMode($level) 98 | { 99 | return parent::setTransactionAccessMode($level); 100 | } 101 | 102 | public function executeAnonymousQuery($sql) 103 | { 104 | return parent::executeAnonymousQuery($sql); 105 | } 106 | 107 | public function getSession() 108 | { 109 | return parent::getSession(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WeirdFixture.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity; 13 | 14 | class WeirdFixture extends FlexibleEntity 15 | { 16 | } 17 | 18 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WeirdFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\ModelManager\Model\RowStructure; 14 | use PommProject\ModelManager\Model\ModelTrait\ReadQueries; 15 | use PommProject\ModelManager\Model\Model; 16 | 17 | class WeirdFixtureModel extends Model 18 | { 19 | use ReadQueries; 20 | 21 | public function __construct() 22 | { 23 | $this->structure = (new RowStructure()) 24 | ->setDefinition(['field_a' => 'int4', 'field_b' => 'bool', 'data_field' => 'varchar']) 25 | ->setRelation("(values (1, 't'::bool, 'one'), (2, 'f'::bool, 'two')) as weird_fixture (field_a, field_b, data_field)") 26 | ->setPrimaryKey(['field_a', 'field_b']) 27 | ; 28 | $this->flexible_entity_class = '\PommProject\ModelManager\Test\Fixture\WeirdFixture'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WithoutPKFixture.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\FlexibleEntity; 13 | 14 | class WithoutPKFixture extends FlexibleEntity 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WithoutPKFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\ModelManager\Model\Model; 14 | use PommProject\ModelManager\Model\ModelTrait\WriteQueries; 15 | 16 | class WithoutPKFixtureModel extends Model 17 | { 18 | use WriteQueries; 19 | 20 | public function __construct() 21 | { 22 | $this->structure = new WithoutPKFixtureStructure(); 23 | $this->flexible_entity_class = '\PommProject\ModelManager\Test\Fixture\WithoutPKFixture'; 24 | } 25 | 26 | public function initialize(Session $session) 27 | { 28 | parent::initialize($session); 29 | $this->dropTable(); 30 | $this->createTable(); 31 | } 32 | 33 | public function shutdown() 34 | { 35 | $this->dropTable(); 36 | } 37 | 38 | protected function createTable() 39 | { 40 | $this->executeAnonymousQuery( 41 | sprintf( 42 | "create temporary table %s (id int4, a_varchar varchar, a_boolean boolean)", 43 | $this->getStructure()->getRelation() 44 | ) 45 | ); 46 | return $this; 47 | } 48 | 49 | protected function dropTable() 50 | { 51 | $this 52 | ->executeAnonymousQuery( 53 | sprintf( 54 | "drop table if exists %s", 55 | $this->getStructure()->getRelation() 56 | ) 57 | ) 58 | ; 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WithoutPKFixtureStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\ModelManager\Model\RowStructure; 13 | 14 | class WithoutPKFixtureStructure extends RowStructure 15 | { 16 | public function __construct() 17 | { 18 | $this 19 | ->setRelation('without_pk_fixture') 20 | ->addField('id', 'int4') 21 | ->addField('a_varchar', 'varchar') 22 | ->addField('a_boolean', 'bool') 23 | ->primary_key = [] 24 | ; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sources/tests/Fixture/WriteFixtureModel.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Fixture; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\ModelManager\Model\ModelTrait\WriteQueries; 14 | 15 | class WriteFixtureModel extends SimpleFixtureModel 16 | { 17 | use WriteQueries; 18 | 19 | public function __construct() 20 | { 21 | parent::__construct(); 22 | $this->getStructure()->setRelation('write_fixture'); 23 | } 24 | 25 | public function initialize(Session $session) 26 | { 27 | parent::initialize($session); 28 | $this 29 | ->dropTable() 30 | ->createTable() 31 | ; 32 | } 33 | 34 | public function shutdown() 35 | { 36 | $this->dropTable(); 37 | } 38 | 39 | protected function createTable() 40 | { 41 | $this->executeAnonymousQuery( 42 | sprintf( 43 | "create temporary table %s (id serial primary key, a_varchar varchar, a_boolean boolean)", 44 | $this->getStructure()->getRelation() 45 | ) 46 | ); 47 | 48 | return $this; 49 | } 50 | 51 | public function truncate() 52 | { 53 | $this->executeAnonymousQuery(sprintf("truncate %s", $this->getStructure()->getRelation())); 54 | } 55 | 56 | protected function dropTable() 57 | { 58 | $this 59 | ->executeAnonymousQuery( 60 | sprintf( 61 | "drop table if exists %s", 62 | $this->getStructure()->getRelation() 63 | ) 64 | ) 65 | ; 66 | 67 | return $this; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sources/tests/Unit/BaseTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit; 11 | 12 | use PommProject\Foundation\Exception\SqlException; 13 | use PommProject\Foundation\Session\Session; 14 | use PommProject\ModelManager\Test\Fixture\ComplexFixtureStructure; 15 | use PommProject\ModelManager\Test\Fixture\ComplexNumberStructure; 16 | use PommProject\ModelManager\Tester\ModelSessionAtoum; 17 | 18 | abstract class BaseTest extends ModelSessionAtoum 19 | { 20 | protected function initializeSession(Session $session) 21 | { 22 | } 23 | 24 | /** 25 | * Because newTestedInstance cannot be called in setUp or tearDown methods, 26 | * we must set up session manually in test methods. 27 | */ 28 | protected function setUpSession(Session $session) 29 | { 30 | $session 31 | ->getPoolerForType('converter') 32 | ->getConverterHolder() 33 | ->registerConverter( 34 | 'ComplexNumber', 35 | $this->newTestedInstance( 36 | 'PommProject\ModelManager\Test\Fixture\ComplexNumber', 37 | new ComplexNumberStructure() 38 | ), 39 | ['pomm_test.complex_number'] 40 | ) 41 | ->registerConverter( 42 | 'ComplexFixture', 43 | $this->newTestedInstance( 44 | '\PommProject\ModelManager\Test\Fixture\ComplexFixture', 45 | new ComplexFixtureStructure() 46 | ), 47 | ['complex_fixture'] 48 | ) 49 | ; 50 | 51 | return $session; 52 | } 53 | 54 | public function setUp() 55 | { 56 | $session = $this->buildSession(); 57 | $sql = 58 | [ 59 | "drop schema if exists pomm_test cascade", 60 | "begin", 61 | "create schema pomm_test", 62 | "create type pomm_test.complex_number as (real float8, imaginary float8)", 63 | "commit", 64 | ]; 65 | 66 | try { 67 | $session->getConnection()->executeAnonymousQuery(join(';', $sql)); 68 | } catch (SqlException $e) { 69 | $session->getConnection()->executeAnonymousQuery('rollback'); 70 | throw $e; 71 | } 72 | } 73 | 74 | public function tearDown() 75 | { 76 | $this 77 | ->buildSession() 78 | ->getConnection() 79 | ->executeAnonymousQuery('drop schema if exists pomm_test cascade') 80 | ; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /sources/tests/Unit/Converter/PgEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Converter; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\Foundation\Converter\PgHstore; 14 | use PommProject\ModelManager\Model\RowStructure; 15 | use PommProject\ModelManager\Test\Fixture\ComplexFixture; 16 | use PommProject\ModelManager\Test\Fixture\ComplexFixtureStructure; 17 | use PommProject\ModelManager\Test\Fixture\ComplexNumber; 18 | use PommProject\ModelManager\Test\Fixture\ComplexNumberStructure; 19 | use PommProject\ModelManager\Test\Unit\BaseTest; 20 | 21 | class PgEntity extends BaseTest 22 | { 23 | protected function initializeSession(Session $session) 24 | { 25 | parent::initializeSession($session); 26 | $session 27 | ->getPoolerForType('converter') 28 | ->getConverterHolder() 29 | ->registerConverter('HStore', new PgHstore(), ['hstore']) 30 | ; 31 | } 32 | 33 | protected function getComplexNumberConverter() 34 | { 35 | return $this->newTestedInstance( 36 | 'PommProject\ModelManager\Test\Fixture\ComplexNumber', 37 | new ComplexNumberStructure() 38 | ); 39 | } 40 | 41 | protected function getComplexFixtureConverter() 42 | { 43 | return $this->newTestedInstance( 44 | 'PommProject\ModelManager\Test\Fixture\ComplexFixture', 45 | new ComplexFixtureStructure() 46 | ); 47 | } 48 | 49 | public function testFromPg() 50 | { 51 | $this 52 | ->assert("Row types are converted into entities.") 53 | ->given( 54 | $entity = $this->getComplexNumberConverter()->fromPg( 55 | '(1.233,2.344)', 56 | 'complex_number', 57 | $this->setUpSession($this->buildSession()) 58 | ) 59 | ) 60 | ->object($entity) 61 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexNumber') 62 | ->float($entity['real']) 63 | ->isEqualTo(1.233) 64 | ->float($entity['imaginary']) 65 | ->isEqualTo(2.344) 66 | ->assert("Null values return null.") 67 | ->given( 68 | $result = $this->getComplexNumberConverter()->fromPg( 69 | null, 70 | 'complex_number', 71 | $this->setUpSession($this->buildSession()) 72 | ) 73 | ) 74 | ->variable($result) 75 | ->isNull() 76 | ; 77 | } 78 | 79 | public function testComplexFromPg() 80 | { 81 | $converter = $this->getComplexFixtureConverter(); 82 | $session = $this->setUpSession($this->buildSession()); 83 | $entity = $converter->fromPg( 84 | '(1,,"(1.233,2.344)","{""(3.455,4.566)"",""(5.677,6.788)"",NULL}","2014-10-24 12:44:40.021324+00","{""1982-04-21 23:12:43+00""}")', 85 | 'complex_fixture', 86 | $session 87 | ); 88 | 89 | $this 90 | ->object($entity) 91 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexFixture') 92 | ->integer($entity['id']) 93 | ->isEqualTo(1) 94 | ->object($entity['complex_number']) 95 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexNumber') 96 | ->array($entity['complex_numbers']) 97 | ->hasSize(3) 98 | ->variable($entity['version_id']) 99 | ->isNull() 100 | ->variable($converter->fromPg('', 'complex_fixture', $session)) 101 | ->isNull() 102 | ; 103 | $converter = $this->newTestedInstance( 104 | 'PommProject\ModelManager\Test\Fixture\ComplexFixture', 105 | (new RowStructure()) 106 | ->setRelation('some_type') 107 | ->addField('a_field', 'int4') 108 | ->addField('a_null_field', 'bool') 109 | ->addField('some_fields', 'int4[]') 110 | ->addField('a_hstore', 'hstore') 111 | ); 112 | $line = <<<"ROW" 113 | (34,,"{4,3}","""pika"" => ""\\\\\\"chu, rechu""") 114 | ROW; 115 | $entity = $converter->fromPg($line, 'some_type', $session); 116 | $this 117 | ->object($entity) 118 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexFixture') 119 | ->integer($entity['a_field']) 120 | ->isEqualTo(34) 121 | ->variable($entity['a_null_field']) 122 | ->isNull() 123 | ->array($entity['some_fields']) 124 | ->isIdenticalTo([4, 3]) 125 | ->array($entity['a_hstore']) 126 | ->isIdenticalTo(['pika' => '\\"chu, rechu']) 127 | ; 128 | } 129 | 130 | public function testFromPgWithJson() 131 | { 132 | $session = $this->setUpSession($this->buildSession()); 133 | $converter = $this->newTestedInstance( 134 | 'PommProject\ModelManager\Test\Fixture\ComplexFixture', 135 | (new RowStructure()) 136 | ->setRelation('some_type') 137 | ->addField('a_field', 'int4') 138 | ->addField('a_null_field', 'bool') 139 | ->addField('a_json', 'jsonb') 140 | ); 141 | $line = <<<"ROW" 142 | (34,,"{""a"": {""b"": ""c\\\\""pika\\\\""""}}") 143 | ROW; 144 | $entity = $converter->fromPg($line, 'some_type', $session); 145 | $this 146 | ->object($entity) 147 | ->array($entity['a_json']) 148 | ->isIdenticalTo(['a' => ['b' => 'c"pika"']]) 149 | ; 150 | } 151 | 152 | public function testToPg($complex_fixture) 153 | { 154 | $converter = $this->getComplexFixtureConverter(); 155 | $session = $this->setUpSession($this->buildSession()); 156 | $string = $converter->toPg($complex_fixture, 'complex_fixture', $session); 157 | 158 | $this 159 | ->string($string) 160 | ->isEqualTo("row(int4 '1',NULL::int4,row(float8 '1.233',float8 '2.344')::pomm_test.complex_number,ARRAY[row(float8 '3.455',float8 '4.566')::pomm_test.complex_number,row(float8 '5.677',float8 '6.788')::pomm_test.complex_number,NULL::pomm_test.complex_number]::pomm_test.complex_number[],timestamptz '2014-10-24 12:44:40.021324+00:00',ARRAY[timestamptz '1982-04-21 23:12:43.000000+00:00']::timestamptz[])::complex_fixture") 161 | ->string($converter->toPg(null, 'complex_fixture', $session)) 162 | ->isEqualTo('NULL::complex_fixture') 163 | ; 164 | } 165 | 166 | protected function testToPgDataProvider() 167 | { 168 | return [ 169 | new ComplexFixture([ 170 | 'id' => 1, 171 | 'version_id' => null, 172 | 'complex_number' => new ComplexNumber(['real' => 1.233, 'imaginary' => 2.344]), 173 | 'complex_numbers' => 174 | [ 175 | new ComplexNumber(['real' => 3.455, 'imaginary' => 4.566]), 176 | new ComplexNumber(['real' => 5.677, 'imaginary' => 6.788]), 177 | null, 178 | ], 179 | 'created_at' => new \DateTime('2014-10-24 12:44:40.021324+00'), 180 | 'updated_at' => [new \DateTime('1982-04-21 23:12:43+00')] 181 | ]), 182 | ]; 183 | } 184 | 185 | public function testInvalidDataToPg() 186 | { 187 | $this->exception(function () { 188 | $converter = $this->getComplexFixtureConverter(); 189 | $session = $this->setUpSession($this->buildSession()); 190 | $invalidData = new \stdClass(); 191 | $converter->toPg($invalidData, 'complex_fixture', $session); 192 | }); 193 | } 194 | 195 | /** 196 | * @dataProvider testToPgDataProvider 197 | */ 198 | public function testToPgStandardFormat($complex_fixture) 199 | { 200 | $converter = $this->getComplexFixtureConverter(); 201 | $session = $this->setUpSession($this->buildSession()); 202 | $row = '(1,,"(1.233,2.344)","{""(3.455,4.566)"",""(5.677,6.788)"",NULL}","2014-10-24 12:44:40.021324+00:00","{""1982-04-21 23:12:43.000000+00:00""}")'; 203 | $model = $session 204 | ->getModel('\PommProject\ModelManager\Test\Fixture\ComplexFixtureModel') 205 | ; 206 | 207 | $this 208 | ->variable($converter->toPgStandardFormat(null, 'complex_fixture', $session)) 209 | ->isNull() 210 | ->string($converter->toPgStandardFormat($complex_fixture, 'complex_fixture', $session)) 211 | ->isEqualTo($row) 212 | ->object($this->sendAsPostgresParameter($complex_fixture, 'complex_fixture', $session)) 213 | ->isInstanceOf('\PommProject\ModelManager\Test\Fixture\ComplexFixture') 214 | ; 215 | } 216 | 217 | private function sendAsPostgresParameter($value, $type, Session $session) 218 | { 219 | $result = $session 220 | ->getQueryManager() 221 | ->query( 222 | sprintf("select $*::%s as my_test", $type), 223 | [$value] 224 | ) 225 | ->current() 226 | ; 227 | 228 | return $result['my_test']; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/CollectionIterator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use Mock\PommProject\ModelManager\Model\CollectionIterator as CollectionIteratorMock; 13 | use Mock\PommProject\ModelManager\Model\Projection as ProjectionMock; 14 | use PommProject\Foundation\Session\Session; 15 | use PommProject\ModelManager\Test\Fixture\SimpleFixtureModel; 16 | use PommProject\ModelManager\Tester\ModelSessionAtoum; 17 | 18 | class CollectionIterator extends ModelSessionAtoum 19 | { 20 | protected $session; 21 | 22 | protected function getSession() 23 | { 24 | if ($this->session === null) { 25 | $this->session = $this->buildSession(); 26 | } 27 | 28 | return $this->session; 29 | } 30 | 31 | protected function initializeSession(Session $session) 32 | { 33 | $session 34 | ->registerClient(new SimpleFixtureModel) 35 | ; 36 | } 37 | 38 | protected function getSql() 39 | { 40 | return <<getSql() : $sql; 52 | 53 | return $this->getSession()->getConnection()->sendQueryWithParameters($sql); 54 | } 55 | 56 | protected function getCollectionMock($sql = null) 57 | { 58 | return new CollectionIteratorMock( 59 | $this->getQueryResult($sql), 60 | $this->getSession(), 61 | new ProjectionMock('\PommProject\ModelManager\Test\Fixture\SimpleFixture', ['id' => 'int4', 'some_data' => 'varchar']) 62 | ); 63 | } 64 | 65 | public function testGetWithoutFilters() 66 | { 67 | $collection = $this->getCollectionMock(); 68 | $this 69 | ->object($collection->get(0)) 70 | ->isInstanceOf('\PommProject\ModelManager\Test\Fixture\SimpleFixture') 71 | ->mock($collection) 72 | ->call('parseRow') 73 | ->atLeastOnce() 74 | ->array($collection->get(0)->extract()) 75 | ->isEqualTo(['Id' => 1, 'SomeData' => 'one']) 76 | ->array($collection->get(3)->extract()) 77 | ->isEqualTo(['Id' => 4, 'SomeData' => 'four']) 78 | ; 79 | } 80 | 81 | public function testGetWithFilters() 82 | { 83 | $collection = $this->getCollectionMock(); 84 | $collection->registerFilter( 85 | function ($values) { $values['id'] *= 2; return $values; } 86 | ) 87 | ->registerFilter( 88 | function ($values) { 89 | $values['some_data'] = 90 | strlen($values['some_data']) > 3 91 | ? null 92 | : $values['some_data']; 93 | ++$values['id']; 94 | $values['new_value'] = 'love pomm'; 95 | 96 | return $values; 97 | } 98 | ); 99 | $this 100 | ->array($collection->get(0)->extract()) 101 | ->isEqualTo(['Id' => 3, 'SomeData' => 'one', 'NewValue' => 'love pomm']) 102 | ->array($collection->get(3)->extract()) 103 | ->isEqualTo(['Id' => 9, 'SomeData' => null, 'NewValue' => 'love pomm']) 104 | ; 105 | } 106 | 107 | public function testGetWithWrongFilter() 108 | { 109 | $collection = $this->getCollectionMock(); 110 | $collection->registerFilter(function ($values) { return $values['id']; }); 111 | $this 112 | ->exception(function () use ($collection) { $collection->get(2); }) 113 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 114 | ->message->contains('Filters MUST return an array') 115 | ; 116 | } 117 | 118 | public function testRegisterBadFilters() 119 | { 120 | $collection = $this->getCollectionMock(); 121 | $this 122 | ->exception(function () use ($collection) { 123 | $collection->registerFilter('whatever'); 124 | }) 125 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 126 | ->message->contains('is not a callable') 127 | ; 128 | } 129 | 130 | public function testExtract() 131 | { 132 | $collection = $this->getCollectionMock(); 133 | 134 | $this 135 | ->array($collection->extract()) 136 | ->isIdenticalTo( 137 | [ 138 | ['Id' => 1, 'SomeData' => 'one'], 139 | ['Id' => 2, 'SomeData' => 'two'], 140 | ['Id' => 3, 'SomeData' => 'three'], 141 | ['Id' => 4, 'SomeData' => 'four'], 142 | ] 143 | ); 144 | } 145 | 146 | public function testSlice() 147 | { 148 | $collection = $this->getCollectionMock(); 149 | 150 | $this 151 | ->array($collection->slice('some_data')) 152 | ->isIdenticalTo( 153 | [ 154 | 'one', 155 | 'two', 156 | 'three', 157 | 'four', 158 | ] 159 | ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/CollectionQuery.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use PommProject\Foundation\Session\Session; 13 | use PommProject\ModelManager\Tester\ModelSessionAtoum; 14 | 15 | class CollectionQuery extends ModelSessionAtoum 16 | { 17 | protected function initializeSession(Session $session) 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/FlexibleEntity/FlexibleContainer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model\FlexibleEntity; 11 | 12 | use Atoum; 13 | 14 | class FlexibleContainer extends Atoum 15 | { 16 | public function testHydrate() 17 | { 18 | $container = $this->newTestedInstance(); 19 | $this 20 | ->object($container->hydrate(["a" => "one"])) 21 | ->isInstanceOf('\PommProject\ModelManager\Model\FlexibleEntity\FlexibleContainer') 22 | ->array($container->fields()) 23 | ->isIdenticalTo(["a" => "one"]) 24 | ->object($container->hydrate(["b" => "two"])) 25 | ->array($container->fields()) 26 | ->isIdenticalTo(["a" => "one", "b" => "two"]) 27 | ->object($container->hydrate(["a" => "three", "c" => "four"])) 28 | ->array($container->fields()) 29 | ->isIdenticalTo(["a" => "three", "b" => "two", "c" => "four"]) 30 | ; 31 | } 32 | 33 | public function testFields() 34 | { 35 | $container = $this->newTestedInstance() 36 | ->hydrate(["a" => "one", "b" => "two", "c" => null]) 37 | ; 38 | $this 39 | ->array($container->fields()) 40 | ->isIdenticalTo(["a" => "one", "b" => "two", "c" => null]) 41 | ->array($container->fields(["a"])) 42 | ->isIdenticalTo(["a" => "one"]) 43 | ->array($container->fields(["a", "c"])) 44 | ->isIdenticalTo(["a" => "one", "c" => null]) 45 | ->array($container->fields([])) 46 | ->isIdenticalTo([]) 47 | ->exception(function () use ($container) { return $container->fields(["d"]); }) 48 | ->isInstanceOf("\InvalidArgumentException") 49 | ->message->contains("{a, b, c}") 50 | ; 51 | } 52 | 53 | public function testExtract() 54 | { 55 | $container = $this->newTestedInstance() 56 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 57 | ; 58 | $this 59 | ->array($container->fields()) 60 | ->isIdenticalTo(["a" => "one", "b" => "two", "c" => "three"]) 61 | ; 62 | } 63 | 64 | public function testGetIterator() 65 | { 66 | $container = $this->newTestedInstance() 67 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 68 | ; 69 | $this 70 | ->object($container->getIterator()) 71 | ->isInstanceOf('\ArrayIterator') 72 | ->array($container->getIterator()->getArrayCopy()) 73 | ->isIdenticalTo(["a" => "one", "b" => "two", "c" => "three"]) 74 | ; 75 | } 76 | 77 | public function testGenericGet() 78 | { 79 | $container = $this->newTestedInstance() 80 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 81 | ; 82 | $this 83 | ->string($container->getA()) 84 | ->isEqualTo("one") 85 | ->string($container->getC()) 86 | ->isEqualTo("three") 87 | ->exception(function () use ($container) { $container->getPika(); }) 88 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 89 | ->message->contains("{a, b, c") 90 | ; 91 | } 92 | 93 | public function testGenericSet() 94 | { 95 | $container = $this->newTestedInstance() 96 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 97 | ; 98 | $this 99 | ->object($container->setPika('chu')) 100 | ->isInstanceOf('\PommProject\ModelManager\Model\FlexibleEntity\FlexibleContainer') 101 | ->array($container->fields()) 102 | ->isIdenticalTo(["a" => "one", "b" => "two", "c" => "three", "pika" => "chu"]) 103 | ->object($container->setA("four")) 104 | ->array($container->fields()) 105 | ->isIdenticalTo(["a" => "four", "b" => "two", "c" => "three", "pika" => "chu"]) 106 | ->object($container->setA(null)) 107 | ->array($container->fields()) 108 | ->isIdenticalTo(["a" => null, "b" => "two", "c" => "three", "pika" => "chu"]) 109 | ; 110 | } 111 | 112 | public function testGenericHas() 113 | { 114 | $container = $this->newTestedInstance() 115 | ->hydrate(["a" => "one", "b" => "two", "c" => null]) 116 | ; 117 | $this 118 | ->boolean($container->hasA()) 119 | ->isTrue() 120 | ->boolean($container->hasPika()) 121 | ->isFalse() 122 | ->boolean($container->hasC()) 123 | ->isTrue() 124 | ; 125 | } 126 | 127 | public function testGenericClear() 128 | { 129 | $container = $this->newTestedInstance() 130 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 131 | ; 132 | $this 133 | ->object($container->clearA()) 134 | ->isInstanceOf('\PommProject\ModelManager\Model\FlexibleEntity\FlexibleContainer') 135 | ->array($container->fields()) 136 | ->isIdenticalTo(["b" => "two", "c" => "three"]) 137 | ->exception(function () use ($container) { $container->clearA(); }) 138 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 139 | ->message->contains("{b, c") 140 | ; 141 | } 142 | 143 | public function testCall() 144 | { 145 | $container = $this->newTestedInstance() 146 | ->hydrate(["a" => "one", "b" => "two", "c" => "three"]) 147 | ; 148 | $this 149 | ->exception(function () use ($container) { $container->pika(); }) 150 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 151 | ->message->contains("No such argument") 152 | ->exception(function () use ($container) { $container->cliPika(); }) 153 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 154 | ->message->contains("No such method") 155 | ; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/IdentityMapper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use Atoum; 13 | use PommProject\ModelManager\Test\Fixture\ComplexFixture; 14 | 15 | class IdentityMapper extends Atoum 16 | { 17 | public function testFetch() 18 | { 19 | $fixture = new ComplexFixture(['created_at' => new \DateTime("2014-10-30 10:13:56.420342+00"), 'some_id' => 1, 'yes' => true ]); 20 | $mapper = $this->newTestedInstance(); 21 | 22 | $this 23 | ->object($mapper->fetch($fixture, ['some_id'])) 24 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexFixture') 25 | ->isIdenticalTo($fixture) 26 | ->object($mapper->fetch(new ComplexFixture(['created_at' => new \DateTime("2013-10-30 10:13:56.420342+00"), 'some_id' => 1, 'yes' => false ]), ['some_id'])) 27 | ->isIdenticalTo($fixture) 28 | ->dateTime($fixture->get('created_at')) 29 | ->hasYear(2013) 30 | ->boolean($fixture->get('yes')) 31 | ->isFalse() 32 | ->object($mapper->clear()) 33 | ->object($mapper->fetch($fixture, ['some_id', 'created_at'])) 34 | ->isInstanceOf('PommProject\ModelManager\Test\Fixture\ComplexFixture') 35 | ->isIdenticalTo($fixture) 36 | ->object($mapper->fetch(new ComplexFixture(['created_at' => new \DateTime("2013-10-30 10:13:56.420342+00"), 'some_id' => 1, 'yes' => true ]), ['some_id', 'created_at'])) 37 | ->isIdenticalTo($fixture) 38 | ->boolean($fixture->get('yes')) 39 | ->isTrue() 40 | ; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/ModelPooler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use PommProject\Foundation\Converter\ConverterHolder; 13 | use PommProject\Foundation\Converter\ConverterPooler; 14 | use PommProject\Foundation\Session\Session; 15 | use PommProject\Foundation\Tester\VanillaSessionAtoum; 16 | 17 | class ModelPooler extends VanillaSessionAtoum 18 | { 19 | protected function initializeSession(Session $session) 20 | { 21 | $session 22 | ->registerClientPooler(new ConverterPooler(new ConverterHolder)) 23 | ->registerClientPooler($this->newTestedInstance()) 24 | ; 25 | } 26 | 27 | public function testGetPoolerType() 28 | { 29 | $this 30 | ->string($this->newTestedInstance()->getPoolerType()) 31 | ->isEqualTo('model') 32 | ; 33 | } 34 | 35 | public function testGetClient() 36 | { 37 | $session = $this->buildSession(); 38 | $model_class = '\PommProject\ModelManager\Test\Fixture\SimpleFixtureModel'; 39 | $model_instance = $session->getClientUsingPooler('model', $model_class); 40 | 41 | $this 42 | ->assert('Client is not in the ClientHolder.') 43 | ->object($model_instance) 44 | ->isInstanceOf($model_class) 45 | ->object($session->getClientUsingPooler('model', $model_class)) 46 | ->isIdenticalTo($model_instance) 47 | ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/Projection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use Atoum; 13 | 14 | class Projection extends Atoum 15 | { 16 | public function testConstructorEmpty() 17 | { 18 | $projection = $this->newTestedInstance('whatever'); 19 | $this 20 | ->object($projection) 21 | ->isInstanceOf('PommProject\ModelManager\Model\Projection') 22 | ->array($projection->getFieldNames()) 23 | ->isEmpty() 24 | ; 25 | } 26 | 27 | public function testConstructorWithParameter() 28 | { 29 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 30 | $this 31 | ->object($projection) 32 | ->isInstanceOf('PommProject\ModelManager\Model\Projection') 33 | ->array($projection->getFieldNames()) 34 | ->isIdenticalTo(['pika']) 35 | ->string($projection->getFieldType('pika')) 36 | ->isEqualTo('int4') 37 | ; 38 | } 39 | 40 | public function testSetField() 41 | { 42 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 43 | $this 44 | ->array($projection->setField('chu', '%:chu:%', 'bool')->getFieldNames()) 45 | ->isIdenticalTo(['pika', 'chu']) 46 | ->string($projection->getFieldType('chu')) 47 | ->isEqualTo('bool') 48 | ->array($projection->setField('chu', '%:chu:%', 'char')->getFieldNames()) 49 | ->isIdenticalTo(['pika', 'chu']) 50 | ->string($projection->getFieldType('chu')) 51 | ->isEqualTo('char') 52 | ->exception(function () use ($projection) { $projection->setField(null, 'whatever', 'whatever'); }) 53 | ->isInstanceOf('\InvalidArgumentException') 54 | ->message->contains('Field name cannot be null.') 55 | ->exception(function () use ($projection) { $projection->setField('whatever', null, 'whatever'); }) 56 | ->isInstanceOf('\InvalidArgumentException') 57 | ->message->contains('Content cannot be null') 58 | ->array($projection->setField('chu', '%:chu:%', null)->getFieldNames()) 59 | ->isIdenticalTo(['pika', 'chu']) 60 | ->variable($projection->getFieldType('chu')) 61 | ->isNull() 62 | ; 63 | } 64 | 65 | public function testSetFieldType() 66 | { 67 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 68 | $this 69 | ->string($projection->setFieldType('pika', 'bool')->getFieldType('pika')) 70 | ->isEqualTo('bool') 71 | ->variable($projection->setFieldType('pika', null)->getFieldType('pika')) 72 | ->isNull() 73 | ->exception(function () use ($projection) { $projection->setFieldType('whatever', 'whatever'); }) 74 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 75 | ->message->contains('does not exist') 76 | ->exception(function () use ($projection) { $projection->setFieldType(null, 'whatever'); }) 77 | ->isInstanceOf('\InvalidArgumentException') 78 | ->message->contains('cannot be null') 79 | ; 80 | } 81 | 82 | public function testUnsetField() 83 | { 84 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 85 | $this 86 | ->array($projection->unsetField('pika')->getFieldNames()) 87 | ->isEmpty() 88 | ->exception(function () use ($projection) { $projection->getFieldType('pika'); }) 89 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 90 | ->message->contains('does not exist') 91 | ->exception(function () use ($projection) { $projection->unsetField('pika'); }) 92 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 93 | ->message->contains('does not exist') 94 | ->exception(function () use ($projection) { $projection->unsetField(null); }) 95 | ->isInstanceOf('\InvalidArgumentException') 96 | ->message->contains('cannot be null') 97 | ; 98 | } 99 | 100 | public function testHasField() 101 | { 102 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 103 | $this 104 | ->boolean($projection->hasField('pika')) 105 | ->isTrue() 106 | ->boolean($projection->hasField('chu')) 107 | ->isFalse() 108 | ->exception(function () use ($projection) { $projection->hasField(null); }) 109 | ->isInstanceOf('\InvalidArgumentException') 110 | ->message->contains('cannot be null') 111 | ; 112 | } 113 | 114 | public function testGetFieldType() 115 | { 116 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 117 | $this 118 | ->string($projection->getFieldType('pika')) 119 | ->isEqualTo('int4') 120 | ->exception(function () use ($projection) { $projection->getFieldType('chu'); }) 121 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 122 | ->message->contains('does not exist') 123 | ->exception(function () use ($projection) { $projection->getFieldType(null); }) 124 | ->isInstanceOf('\InvalidArgumentException') 125 | ->message->contains('cannot be null') 126 | ; 127 | } 128 | 129 | public function testIsArray() 130 | { 131 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 132 | $this->boolean($projection->isArray('pika')) 133 | ->isFalse() 134 | ->boolean($projection->setField('chu', '%:chu:%', 'int4[]')->isArray('chu')) 135 | ->isTrue() 136 | ->exception(function () use ($projection) { $projection->isArray('whatever'); }) 137 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 138 | ->message->contains('does not exist') 139 | ->exception(function () use ($projection) { $projection->isArray(null); }) 140 | ->isInstanceOf('\InvalidArgumentException') 141 | ->message->contains('Field name cannot be null.') 142 | ; 143 | } 144 | 145 | public function testGetFieldNames() 146 | { 147 | $projection = $this->newTestedInstance('whatever'); 148 | $this->array($projection->getFieldNames()) 149 | ->isEmpty() 150 | ->array($projection->setField('pika', '%:chu:%', 'int4')->getFieldNames()) 151 | ->isIdenticalTo(['pika']) 152 | ->array($projection->setField('chu', '%:chu:%', 'char')->getFieldNames()) 153 | ->isIdenticalTo(['pika', 'chu']) 154 | ; 155 | } 156 | 157 | public function testGetFieldTypes() 158 | { 159 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']) 160 | ->setField('chu', 'expression(chu)') 161 | ->setField('plop', 'plop', 'string') 162 | ; 163 | 164 | $this 165 | ->array($projection->getFieldTypes()) 166 | ->isIdenticalTo(['pika' => 'int4', 'chu' => null, 'plop' => 'string']) 167 | ; 168 | } 169 | 170 | public function testGetFieldWithTableAlias() 171 | { 172 | $projection = $this->newTestedInstance('whatever', ['pika' => 'int4']); 173 | $this->string($projection->getFieldWithTableAlias('pika')) 174 | ->isEqualTo('"pika"') 175 | ->string($projection->getFieldWithTableAlias('pika', 'my_table')) 176 | ->isEqualTo('my_table."pika"') 177 | ->string($projection->setField('chu', '%:pika:% / 2', 'int4')->getFieldWithTableAlias('chu', 'my_table')) 178 | ->isEqualTo('my_table."pika" / 2') 179 | ->exception(function () use ($projection) { $projection->getFieldWithTableAlias('whatever'); }) 180 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 181 | ->message->contains('does not exist') 182 | ->exception(function () use ($projection) { $projection->getFieldWithTableAlias(null); }) 183 | ->isInstanceOf('\InvalidArgumentException') 184 | ->message->contains('Field name cannot be null.') 185 | ; 186 | } 187 | 188 | public function testGetFieldsWithTableAlias() 189 | { 190 | $projection = $this->newTestedInstance('whatever'); 191 | $this->array($projection->getFieldsWithTableAlias()) 192 | ->isEmpty() 193 | ->array($projection->getFieldsWithTableAlias('my_table')) 194 | ->isEmpty() 195 | ->array($projection->getFieldsWithTableAlias(null)) 196 | ->isEmpty() 197 | ->array($projection->setField('pika', '%:pika:%', 'int4')->getFieldsWithTableAlias()) 198 | ->isIdenticalTo(['pika' => '"pika"']) 199 | ->array($projection->getFieldsWithTableAlias('my_table')) 200 | ->isIdenticalTo(['pika' => 'my_table."pika"']) 201 | ->array($projection->setField('chu', '%:chu:%', 'int4')->getFieldsWithTableAlias()) 202 | ->isIdenticalTo(['pika' => '"pika"', 'chu' => '"chu"']) 203 | ->array($projection->getFieldsWithTableAlias('my_table')) 204 | ->isIdenticalTo(['pika' => 'my_table."pika"', 'chu' => 'my_table."chu"']) 205 | ; 206 | } 207 | 208 | public function testFormatFields() 209 | { 210 | $projection = $this->newTestedInstance('whatever'); 211 | $this->string($projection->formatFields()) 212 | ->isEmpty() 213 | ->string($projection->formatFields('my_table')) 214 | ->isEmpty() 215 | ->string($projection->formatFields(null)) 216 | ->isEmpty() 217 | ->string($projection->setField('pika', '%:pika:%', 'int4')->formatFields()) 218 | ->isEqualTo('"pika"') 219 | ->string($projection->formatFields('my_table')) 220 | ->isEqualTo('my_table."pika"') 221 | ->string($projection->formatFields(null)) 222 | ->isEqualTo('"pika"') 223 | ->string($projection->setField('chu', '%:pika:% / 2', 'int4')->formatFields()) 224 | ->isEqualTo('"pika", "pika" / 2') 225 | ->string($projection->formatFields('my_table')) 226 | ->isEqualTo('my_table."pika", my_table."pika" / 2') 227 | ; 228 | } 229 | 230 | public function testFormatFieldsWithTableAlias() 231 | { 232 | $projection = $this->newTestedInstance('whatever'); 233 | $this->string($projection->formatFieldsWithFieldAlias()) 234 | ->isEmpty() 235 | ->string($projection->formatFieldsWithFieldAlias('my_table')) 236 | ->isEmpty() 237 | ->string($projection->formatFieldsWithFieldAlias(null)) 238 | ->isEmpty() 239 | ->string($projection->setField('pika', '%:pika:%', 'int4')->formatFieldsWithFieldAlias()) 240 | ->isEqualTo('"pika" as "pika"') 241 | ->string($projection->formatFieldsWithFieldAlias('my_table')) 242 | ->isEqualTo('my_table."pika" as "pika"') 243 | ->string($projection->formatFieldsWithFieldAlias(null)) 244 | ->isEqualTo('"pika" as "pika"') 245 | ->string($projection->setField('chu', '%:pika:% / 2', 'int4')->formatFieldsWithFieldAlias()) 246 | ->isEqualTo('"pika" as "pika", "pika" / 2 as "chu"') 247 | ->string($projection->formatFieldsWithFieldAlias('my_table')) 248 | ->isEqualTo('my_table."pika" as "pika", my_table."pika" / 2 as "chu"') 249 | ; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /sources/tests/Unit/Model/RowStructure.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\Model; 11 | 12 | use Atoum; 13 | use PommProject\ModelManager\Model\RowStructure as PommRowStructure; 14 | 15 | class RowStructure extends Atoum 16 | { 17 | public function testInherits() 18 | { 19 | $structure = new GoodStructure(); 20 | $this->object($structure->inherits(new ChuStructure())) 21 | ->isInstanceOf('\PommProject\ModelManager\Model\RowStructure') 22 | ->array($structure->getDefinition()) 23 | ->isIdenticalTo(['pika' => 'int4', 'chu' => 'bool']) 24 | ; 25 | } 26 | 27 | public function testAddField() 28 | { 29 | $structure = new GoodStructure(); 30 | $this->array($structure->getDefinition()) 31 | ->isIdenticalTo(['pika' => 'int4']) 32 | ->array($structure->addField('chu', 'bool')->getDefinition()) 33 | ->isIdenticalTo(['pika' => 'int4', 'chu' => 'bool']) 34 | ->exception(function () use ($structure) { $structure->addField(null, 'int4'); }) 35 | ->isinstanceof('\InvalidArgumentException') 36 | ->message->contains("'name' cannot be null") 37 | ->exception(function () use ($structure) { $structure->addField('name', null); }) 38 | ->isinstanceof('\InvalidArgumentException') 39 | ->message->contains("'type' cannot be null") 40 | ; 41 | } 42 | 43 | public function testGetFieldNames() 44 | { 45 | $structure = new GoodStructure(); 46 | $this->array($structure->getFieldNames()) 47 | ->isIdenticalTo(['pika']) 48 | ->array($structure->addField('chu', 'bool')->getFieldNames()) 49 | ->isIdenticalTo(['pika', 'chu']) 50 | ; 51 | } 52 | 53 | public function testHasField() 54 | { 55 | $structure = new GoodStructure(); 56 | $this->boolean($structure->hasField('pika')) 57 | ->isTrue() 58 | ->boolean($structure->hasField('chu')) 59 | ->isFalse() 60 | ->boolean($structure->addField('chu', 'bool')->hasField('chu')) 61 | ->isTrue() 62 | ; 63 | } 64 | 65 | public function testGetTypeFor() 66 | { 67 | $structure = new GoodStructure(); 68 | $this->string($structure->getTypeFor('pika')) 69 | ->isEqualTo('int4') 70 | ->exception(function () use ($structure) { $structure->getTypeFor(null); }) 71 | ->isinstanceof('\InvalidArgumentException') 72 | ->message->contains("'name' cannot be null") 73 | ->exception(function () use ($structure) { $structure->getTypeFor('chu'); }) 74 | ->isinstanceof('\PommProject\ModelManager\Exception\ModelException') 75 | ->message->contains("Field 'chu' is not defined") 76 | ->string($structure->addField('chu', 'bool')->getTypeFor('chu')) 77 | ->isEqualTo('bool') 78 | ; 79 | } 80 | 81 | public function testGetDefinition() 82 | { 83 | $structure = new GoodStructure(); 84 | $this->array($structure->getDefinition()) 85 | ->isIdenticalTo(['pika' => 'int4']) 86 | ->array($structure->addField('chu', 'bool')->getDefinition()) 87 | ->isIdenticalTo(['pika' => 'int4', 'chu' => 'bool']) 88 | ; 89 | } 90 | 91 | public function testGetRelation() 92 | { 93 | $structure = new GoodStructure(); 94 | $this->string($structure->getRelation()) 95 | ->isEqualTo('pika') 96 | ; 97 | } 98 | 99 | public function testGetPrimaryKey() 100 | { 101 | $structure = new GoodStructure(); 102 | $this->array($structure->getPrimaryKey()) 103 | ->isEmpty() 104 | ; 105 | $structure = new ChuStructure(); 106 | $this->array($structure->getPrimaryKey()) 107 | ->isIdenticalTo(['chu']) 108 | ; 109 | } 110 | 111 | public function testArrayAccess() 112 | { 113 | $structure = new GoodStructure(); 114 | $this->string($structure['pika']) 115 | ->isEqualTo('int4') 116 | ; 117 | $structure['chu'] = 'bool'; 118 | $this->boolean(isset($structure['chu'])) 119 | ->isTrue() 120 | ->exception(function () use ($structure) { unset($structure['chu']); }) 121 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelException') 122 | ->message->contains('Cannot unset a structure field') 123 | ; 124 | } 125 | } 126 | 127 | class GoodStructure extends PommRowStructure 128 | { 129 | public function __construct() 130 | { 131 | $this->relation = 'pika'; 132 | $this->field_definitions['pika'] = 'int4'; 133 | } 134 | } 135 | 136 | class ChuStructure extends PommRowStructure 137 | { 138 | public function __construct() 139 | { 140 | $this->relation = 'chu'; 141 | $this->field_definitions['chu'] = 'bool'; 142 | $this->primary_key = ['chu']; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /sources/tests/Unit/ModelLayer/ModelLayer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PommProject\ModelManager\Test\Unit\ModelLayer; 11 | 12 | use PommProject\Foundation\Session\Connection; 13 | use PommProject\Foundation\Session\Session; 14 | use PommProject\ModelManager\Tester\ModelSessionAtoum; 15 | 16 | class ModelLayer extends ModelSessionAtoum 17 | { 18 | public function setUp() 19 | { 20 | $this 21 | ->buildSession() 22 | ->getConnection() 23 | ->executeAnonymousQuery(<<buildSession() 36 | ->getConnection() 37 | ->executeAnonymousQuery('drop schema pomm_test cascade;') 38 | ; 39 | } 40 | 41 | public function afterTestMethod($method) 42 | { 43 | /* 44 | * This is to ensure the transaction is terminated even if a test fails 45 | * so the ClientHolder can shutdown correctly. 46 | */ 47 | $this->getModelLayer()->rollbackTransaction(); 48 | } 49 | 50 | protected function initializeSession(Session $session) 51 | { 52 | } 53 | 54 | public function getModelLayer() 55 | { 56 | $model_layer = $this->buildSession()->getModelLayer('PommProject\ModelManager\Test\Fixture\SimpleModelLayer'); 57 | $this 58 | ->object($model_layer) 59 | ->isInstanceOf('\PommProject\ModelManager\ModelLayer\ModelLayer') 60 | ; 61 | 62 | return $model_layer; 63 | } 64 | 65 | public function testSetDeferrable() 66 | { 67 | $model_layer = $this->getModelLayer(); 68 | $this 69 | ->object( 70 | $model_layer 71 | ->setDeferrable(['pomm_test.chu_pika_id_fkey'], Connection::CONSTRAINTS_DEFERRED) 72 | ) 73 | ->isEqualTo($model_layer) 74 | ->exception(function () use ($model_layer) { 75 | $model_layer->setDeferrable(['pomm_test.chu_pika_id_fkey'], 'chu'); 76 | }) 77 | ->isInstanceOf('\PommProject\ModelManager\Exception\ModelLayerException') 78 | ->message->contains("'chu' is not a valid constraint modifier") 79 | ; 80 | } 81 | 82 | public function testTransaction() 83 | { 84 | $model_layer = $this->getModelLayer(); 85 | $this 86 | ->boolean($model_layer->startTransaction()) 87 | ->isTrue() 88 | ->boolean($model_layer->setSavepoint('12 345')) 89 | ->isTrue() 90 | ->boolean($model_layer->releaseSavepoint('12 345')) 91 | ->isTrue() 92 | ->boolean($model_layer->setSavepoint('chu')) 93 | ->isTrue() 94 | ->boolean($model_layer->rollbackTransaction('chu')) 95 | ->isTrue() 96 | ->variable($model_layer->sendNotify('plop', 'whatever')) 97 | ->isNull() 98 | ->boolean($model_layer->isTransactionOk()) 99 | ->isTrue() 100 | ->exception(function () use ($model_layer) { $model_layer->releaseSavepoint('not exist'); }) 101 | ->isInstanceOf('\PommProject\Foundation\Exception\SqlException') 102 | ->boolean($model_layer->isInTransaction()) 103 | ->isTrue() 104 | ->boolean($model_layer->isTransactionOk()) 105 | ->isFalse() 106 | ->object($model_layer->commitTransaction()) 107 | ->isIdenticalTo($model_layer) 108 | ->array($model_layer->sendNotify('plop', 'whatever')) 109 | ->contains('whatever') 110 | ; 111 | } 112 | 113 | protected function testSetTransactionIsolationLevelDataProvider() 114 | { 115 | return [ 116 | Connection::ISOLATION_READ_COMMITTED, 117 | Connection::ISOLATION_REPEATABLE_READ, 118 | Connection::ISOLATION_SERIALIZABLE, 119 | ]; 120 | } 121 | 122 | public function testSetTransactionIsolationLevel($level) 123 | { 124 | $model_layer = $this->getModelLayer(); 125 | $model_layer->startTransaction(); 126 | $this 127 | ->object($model_layer->setTransactionIsolationLevel($level)) 128 | ->string($this->getTransactionIsolationLevel($model_layer)) 129 | ->isEqualTo(strtolower($level)) 130 | ; 131 | $model_layer->rollbackTransaction(); 132 | } 133 | 134 | protected function getTransactionIsolationLevel($model_layer) 135 | { 136 | return $model_layer->getSession() 137 | ->getQueryManager() 138 | ->query('show transaction_isolation') 139 | ->current()['transaction_isolation'] 140 | ; 141 | } 142 | 143 | public function testSetTransactionAccessMode() 144 | { 145 | $model_layer = $this->getModelLayer(); 146 | $model_layer->startTransaction(); 147 | $this 148 | ->object($model_layer->setTransactionAccessMode(Connection::ACCESS_MODE_READ_WRITE)) 149 | ->string($this->getTransactionAccessModel($model_layer)) 150 | ->isEqualTo('off') 151 | ->object($model_layer->setTransactionAccessMode(Connection::ACCESS_MODE_READ_ONLY)) 152 | ->string($this->getTransactionAccessModel($model_layer)) 153 | ->isEqualTo('on') 154 | ; 155 | } 156 | 157 | protected function getTransactionAccessModel($model_layer) 158 | { 159 | return $model_layer->getSession() 160 | ->getQueryManager() 161 | ->query('show transaction_read_only') 162 | ->current()['transaction_read_only'] 163 | ; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /sources/tests/config.php.dist: -------------------------------------------------------------------------------- 1 | 'pgsql://user:pass@host:port/db_name', 5 | 'name' => 'pomm_test' 6 | ]; 7 | -------------------------------------------------------------------------------- /sources/tests/config.travis.php: -------------------------------------------------------------------------------- 1 | 'pgsql://postgres@127.0.0.1/pomm_test' 5 | ]; 6 | --------------------------------------------------------------------------------