├── .atoum.php ├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── bin └── boomgo ├── composer.json ├── src └── Boomgo │ ├── Builder │ ├── Definition.php │ ├── Generator │ │ ├── AbstractGenerator.php │ │ ├── GeneratorInterface.php │ │ └── MapperGenerator.php │ ├── Map.php │ ├── MapBuilder.php │ ├── Templates │ │ └── Mapper.php.twig │ └── TwigMapperBuilder.php │ ├── Cache │ ├── ArrayCache.php │ ├── CacheInterface.php │ └── NoCache.php │ ├── Console │ └── Command │ │ └── MapperGeneratorCommand.php │ ├── Formatter │ ├── CamelCaseFormatter.php │ ├── FormatterInterface.php │ ├── TransparentFormatter.php │ └── Underscore2CamelFormatter.php │ ├── Mapper │ ├── MapperInterface.php │ ├── MapperProvider.php │ └── SimpleMapper.php │ ├── Parser │ ├── AnnotationParser.php │ └── ParserInterface.php │ └── console.php └── tests └── Boomgo └── Tests ├── Units ├── Builder │ ├── Definition.php │ ├── Generator │ │ ├── AbstractGenerator.php │ │ └── MapperGenerator.php │ ├── Map.php │ └── MapBuilder.php ├── Cache │ ├── ArrayCache.php │ └── NoCache.php ├── Fixture │ ├── Annotation.php │ ├── AnnotationInvalidBoomgo.php │ ├── AnnotationInvalidVar.php │ ├── Annoted │ │ ├── Document.php │ │ └── DocumentEmbed.php │ ├── AnotherAnnoted │ │ └── Document.php │ └── NoNamespace.php ├── Formatter │ ├── CamelCaseFormatter.php │ ├── TransparentFormatter.php │ └── Underscore2CamelFormatter.php ├── Parser │ └── AnnotationParser.php └── Test.php └── bootstrap.php /.atoum.php: -------------------------------------------------------------------------------- 1 | setBootstrapFile(__DIR__.'/tests/Boomgo/Tests/bootstrap.php'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | vendor/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | phps: 3 | - 5.3 4 | - 5.4 5 | - 5.5 6 | before_script: 7 | - wget -nc -nv http://getcomposer.org/composer.phar 8 | - php composer.phar install --dev --prefer-source --no-progress 9 | - php composer.phar dump-autoload --optimize 10 | script: php vendor/bin/atoum -c .atoum.php -d tests 11 | branches: 12 | only: 13 | - master 14 | notifications: 15 | email: 16 | - ludo.fleury@gmail.com 17 | - dguyon@gmail.com -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 Ludovic Fleury 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Boomgo, a micro PHP ODM for [MongoDB](http://www.mongodb.org/) 2 | ============================================================== 3 | 4 | _Boomgo still a work in progress and is initially developped for [Retentio](http://retent.io)_ 5 | 6 | Boomgo is a **light** and **simple** Object Document Mapper on top of the [MongoDB php native driver](http://php.net/mongo). 7 | 8 | [![Build Status](https://secure.travis-ci.org/Retentio/Boomgo.png)](http://travis-ci.org/Retentio/Boomgo) 9 | 10 | Philosophy 11 | ------------- 12 | 13 | Boomgo ODM focuses on the mapping process between PHP objects and MongoDB Documents, It doesn't abstract any feature provided by the native php driver. This way, Boomgo allows you to **keep the full control about your MongoDB interactions** (querying, map reduce, ...). 14 | 15 | _In short, Boomgo offers a handy way to manipulate your MongoDB Documents with PHP Objects._ 16 | 17 | Features 18 | -------- 19 | 20 | Boomgo generate Mappers for your php object, which allow you to: 21 | 22 | * Hydrate PHP Object from a MongoDB results set. 23 | * Serialize PHP Object to mongo-storable array. 24 | * Handle hydration process of embedded document / collection. 25 | 26 | Requirements 27 | ------------ 28 | 29 | Boomgo was built with a lot of love (including best practices & standards). 30 | It will only work for **PHP 5.3+ projects** which use a **structure matching** [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md). 31 | Furthermore, [composer](http://getcomposer.org/) usage is strongly encouraged. 32 | 33 | Installation 34 | ------------ 35 | 36 | ### Composer 37 | 38 | First, in your composer.json, add the requirement line for Boomgo. 39 | 40 | ```json 41 | { 42 | "require": { 43 | "retentio/boomgo": "dev-master" 44 | } 45 | } 46 | ``` 47 | 48 | Then get composer and run the install command. 49 | 50 | ```bash 51 | $ wget -nc -nv http://getcomposer.org/composer.phar 52 | $ php composer.phar install 53 | ``` 54 | 55 | Usage 56 | ----- 57 | 58 | At the moment, Boomgo supports only annotation definition. Yet **it uses only a single tag**: by default "@Persistent" (you can change it). 59 | To persist some attributes of your model, Boomgo needs 3 things : 60 | 61 | 1. A dedicated & unique namespace part for your persisted classes (default "Document"). 62 | 1. The "@Persistent" tag in the property docblock. 63 | 2. Getter & Setter for this property. 64 | 65 | 66 | ### Simple persistence 67 | 68 | ```php 69 | myField; 84 | } 85 | 86 | public function setMyField($value) 87 | { 88 | $this->myField = $value; 89 | } 90 | } 91 | 92 | ?> 93 | ``` 94 | 95 | Then, you can generate your mapper with the command (if you used composer): 96 | 97 | ```bash 98 | $ vendor/bin/boomgo generate:mappers path/to/your/document/folder 99 | ``` 100 | 101 | Boomgo will generate (default: aside of your documents folder) a mapper class called `VendorName\Project\Mapper\MyPersistedClassMapper`. 102 | The mapper exposes 3 methods: 103 | 104 | * `->serialize($yourObject)` 105 | * `->unserialize($yourArray)` 106 | * `->hydrate($yourObject, $yourArray)` 107 | 108 | Then, the usage becomes really simple: 109 | 110 | ```php 111 | setMyField('my value'); 119 | 120 | // Create the mapper 121 | $mapper = new \VendorName\Project\Mapper\MyPersistedClassMapper(); 122 | 123 | // Serialize your object to a mongoable array 124 | $mongoableArray = $mapper->serialize($object); 125 | 126 | // Save with the native php driver 127 | $mongo->selectDB('my_db') 128 | ->selectCollection('my_collection') 129 | ->save($mongoableArray); 130 | 131 | // Fetch a result with the native driver 132 | $result = $mongo->selectDB('my_db') 133 | ->selectCollection('my_collection') 134 | ->findOne(array('myField' => 'my value')); 135 | 136 | // Unserialize the result to an object 137 | $object = $mapper->unserialize($result); 138 | $object->getMyField(); 139 | 140 | // You could also hydrate an existing object from a result 141 | $object = new \VendorName\Project\Document\MyPersistedClass(); 142 | $mapper->hydrate($object, $result); 143 | 144 | ?> 145 | ``` 146 | 147 | ### Advanced persistence 148 | 149 | Boomgo handles **native PHP Mongo types** (MongoId, etc.), **embedded documents** and **nested collections**. 150 | Since, Boomgo love simple & efficient things, annotation are not used for that. Instead it rely on... docblock with the famous under-used @var tag. 151 | 152 | ```php 153 | 198 | ``` 199 | 200 | After mapper generation, usage is almost the same and stay explicit, Boomgo doesn't hide magic. 201 | 202 | ```php 203 | setId(new \MongoId()); 211 | $object->setMyField('my value'); 212 | $object->setMyArray(array('many','values')); 213 | $object->setMyVar('anything'); 214 | $object->setEmbeddedDocument(new \VendorName\Project\Document\EmbeddedDocument()); 215 | $object->setEmbeddedCollection(array(new \VendorName\Project\Document\EmbeddedDocument())); 216 | 217 | // Create the mapper 218 | $mapper = new \VendorName\Project\Mapper\DocumentClassMapper(); 219 | 220 | // Serialize your object to a mongoable array 221 | $mongoableArray = $mapper->serialize($object); 222 | 223 | // Save with the native php driver 224 | $mongo->selectDB('my_db') 225 | ->selectCollection('my_collection') 226 | ->save($mongoableArray); 227 | 228 | // Fetch a result with the native driver 229 | $result = $mongo->selectDB('my_db') 230 | ->selectCollection('my_collection') 231 | ->findOne(array('myField' => 'my value')); 232 | 233 | // Unserialize the result to an object 234 | $object = $mapper->unserialize($result); 235 | 236 | ?> 237 | ``` 238 | 239 | To see the full list of supported type/pseudo type in the @var tag you can look at [Boomgo\Builder\Definition](https://github.com/Retentio/Boomgo/blob/master/src/Boomgo/Builder/Definition.php#L39) 240 | Note that Boomgo won't cast or validate anything, it's only used in the mapping process for normalization & nested documents/collections. 241 | 242 | Limitations 243 | ----------- 244 | 245 | * It doesn't & won't manage object relation for a simple reason: [MongoDB is a NON-RELATIONAL storage](http://www.mongodb.org/display/DOCS/Database+References). 246 | * It doesn't & won't provide [identity map](http://en.wikipedia.org/wiki/Identity_map) etc. 247 | * It doesn't & won't make coffee. 248 | 249 | If you're looking for full-featured php ODM, you should look at [Mandango](https://github.com/mandango/mandango) which use active record/class generator implementation, and also [Doctrine MongoDB ODM](http://www.doctrine-project.org/projects/mongodb_odm/current/docs/en) data-mapping implementation. 250 | 251 | Known issues 252 | ------------ 253 | 254 | * Only MongoId native type is supported, yet it's really easy to add and test other native type. 255 | * Boomgo doesn't fit totally the PSR-0 (actually do not handle underscored class name) 256 | * Boomgo formatters need improvement/refacto 257 | 258 | 259 | Roadmap 260 | ------- 261 | 262 | * Provide a manager. 263 | * Add functional tests. 264 | * More parsers (yml, xml and json). 265 | * ActiveRecord implementation. 266 | * Provide more alternatives for mappers generation (like flat file structures). 267 | * Document classes generation (getters & setters, JsonSerializable interface from php 5.4). 268 | * Json document preview. 269 | * Dynamic mapping using live discrimination. 270 | 271 | Feel free to contribute ! 272 | 273 | How to run unit tests 274 | --------------------- 275 | 276 | Boomgo is unit tested with [atoum](https://github.com/mageekguy/atoum), the dependency is not shipped by default, with composer you have to run the command 277 | 278 | ```bash 279 | $ php composer.phar install --dev --prefer-source 280 | ``` 281 | 282 | To run the complete test suite, open a shell and type : 283 | 284 | ```bash 285 | $ cd path/to/Boomgo 286 | $ php vendor/bin/atoum -c .atoum.php -d tests 287 | ``` 288 | 289 | Want to test on a single class while contributing ? Here is an example with _AnnotationParser_ class : 290 | 291 | ```bash 292 | $ php vendor/bin/atoum -c .atoum.php -f tests/Boomgo/Tests/Units/Parser/AnnotationParser.php 293 | ``` 294 | 295 | Framework integration 296 | --------------------- 297 | 298 | Boomgo already have integration for: 299 | 300 | * Symfony2 with [PlemiBoomgoBundle](https://github.com/Plemi/PlemiBoomgoBundle) 301 | 302 | Credits 303 | ------- 304 | 305 | Boomgo was built thanks to many open source projects & some awesome guys: 306 | 307 | * [Atoum](https://github.com/mageekguy/atoum): the KISS framework for unit test. 308 | * [Composer](http://getcomposer.org/): the awesome dependency manager. 309 | * [Symfony](http://symfony.com/): the components lib is a time-saver. 310 | * [Twig Generator](https://github.com/cedriclombardot/TwigGenerator): the cool tool for code generation with twig. 311 | * [Doctrine](http://www.doctrine-project.org/), [Mandango](http://mandango.org/): ORM/ODM inspiration. 312 | * [@willdurand](https://github.com/willdurand), helped me with a lot of tips. 313 | * [@jpetitcolas](https://github.com/jpetitcolas), regex master. 314 | * [@Palleas](https://github.com/Palleas), ninja supporter forever. -------------------------------------------------------------------------------- /bin/boomgo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =5.3.2", 23 | "symfony/finder": "~2.2.0", 24 | "symfony/console": "~2.2.0", 25 | "cedriclombardot/twig-generator": "dev-master" 26 | }, 27 | "require-dev": { 28 | "mageekguy/atoum": "dev-master" 29 | }, 30 | "autoload": { 31 | "psr-0": { "Boomgo": "src/" } 32 | }, 33 | "bin": ["bin/boomgo"] 34 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Definition.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder; 16 | 17 | /** 18 | * Definition 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | class Definition 23 | { 24 | const DOCUMENT = 'document'; 25 | const COLLECTION = 'collection'; 26 | 27 | /** 28 | * @var array Native types supported by MongoDB php extension 29 | */ 30 | static public $nativeClasses = array( 31 | '\\MongoId' => true); 32 | 33 | /** 34 | * @var array Supported primitive & pseudo types 35 | */ 36 | static public $supportedTypes = array( 37 | 'int' => 'scalar', 38 | 'integer' => 'scalar', 39 | 'bool' => 'scalar', 40 | 'boolean' => 'scalar', 41 | 'float' => 'scalar', 42 | 'double' => 'scalar', 43 | 'real' => 'scalar', 44 | 'string' => 'scalar', 45 | 'number' => 'scalar', 46 | 'mixed' => 'composite', 47 | 'array' => 'composite', 48 | 'object' => 'composite'); 49 | 50 | /** 51 | * @var string Php attribute name 52 | */ 53 | private $attribute; 54 | 55 | /** 56 | * @var string Document key name 57 | */ 58 | private $key; 59 | 60 | /** 61 | * @var string Php (pseudo) type (string, number) 62 | */ 63 | private $type; 64 | 65 | /** 66 | * @var string Embedded type: document/one or collection/many 67 | */ 68 | private $mappedType; 69 | 70 | /** 71 | * @var string FQDN of the mapped/embedded class 72 | */ 73 | private $mappedClass; 74 | 75 | /** 76 | * @var string Php mutator (setter) name 77 | */ 78 | private $mutator; 79 | 80 | /** 81 | * @var string Php accessor (getter) name 82 | */ 83 | private $accessor; 84 | 85 | /** 86 | * Check is a string is a valid namespace 87 | * 88 | * @param string $string 89 | * 90 | * @return boolean 91 | * 92 | * @todo refacto & move this method 93 | */ 94 | static public function isValidNamespace($string) 95 | { 96 | return (bool) preg_match('#^(?=(?:\w|\\\\)*\\\\+(?:\w|\\\\)*)(?:(?:\w|\\\\)+)$#', $string); 97 | } 98 | 99 | /** 100 | * Construct the definition from a metadata array 101 | * 102 | * @param array $metadata 103 | */ 104 | public function __construct(array $metadata) 105 | { 106 | $this->fromArray($metadata); 107 | } 108 | 109 | /** 110 | * Return the php attribue name for this definition 111 | * 112 | * @return string 113 | */ 114 | public function __toString() 115 | { 116 | return $this->attribute; 117 | } 118 | 119 | /** 120 | * Return the (pseudo) type (string, number) 121 | * 122 | * @return string 123 | */ 124 | public function getType() 125 | { 126 | return $this->type; 127 | } 128 | 129 | /** 130 | * Return the php attribute name 131 | * 132 | * @return string 133 | */ 134 | public function getAttribute() 135 | { 136 | return $this->attribute; 137 | } 138 | 139 | /** 140 | * Return the document key name 141 | * 142 | * @return string 143 | */ 144 | public function getKey() 145 | { 146 | return $this->key; 147 | } 148 | 149 | /** 150 | * Return the php mutator (setter) name 151 | * 152 | * @return string 153 | */ 154 | public function getMutator() 155 | { 156 | return $this->mutator; 157 | } 158 | 159 | /** 160 | * Return the php accessor (getter) name 161 | * 162 | * @return string 163 | */ 164 | public function getAccessor() 165 | { 166 | return $this->accessor; 167 | } 168 | 169 | /** 170 | * Check whether a type is composite (i.e. non-scalar) 171 | * 172 | * @return boolean 173 | */ 174 | public function isComposite() 175 | { 176 | return (isset(static::$supportedTypes[$this->type]) && static::$supportedTypes[$this->type] == 'composite'); 177 | } 178 | 179 | /** 180 | * Check whether this definition is mapped to a class 181 | * 182 | * Return true if the mapping is an embedded collection/document 183 | * 184 | * @return boolean 185 | */ 186 | public function isMapped() 187 | { 188 | return (bool) $this->mappedClass; 189 | } 190 | 191 | /** 192 | * Return the mapped type (collection/many or document/one) 193 | * 194 | * @return string 195 | */ 196 | public function getMappedType() 197 | { 198 | return $this->mappedType; 199 | } 200 | 201 | /** 202 | * Return the mapped class FQDN 203 | * 204 | * @return string 205 | */ 206 | public function getMappedClass() 207 | { 208 | return $this->mappedClass; 209 | } 210 | 211 | /** 212 | * Return the mapped short class name 213 | * 214 | * @return string 215 | */ 216 | public function getMappedClassName() 217 | { 218 | $array = explode('\\', $this->mappedClass); 219 | 220 | return $array[count($array)-1]; 221 | } 222 | 223 | /** 224 | * Return the mapped namespace without the short class name 225 | * 226 | * @return string 227 | */ 228 | public function getMappedNamespace() 229 | { 230 | $array = explode('\\', $this->mappedClass); 231 | unset($array[count($array)-1]); 232 | 233 | return implode('\\', $array); 234 | } 235 | 236 | /** 237 | * Check if the embedded definition handle a single document 238 | * 239 | * @return boolean 240 | */ 241 | public function isDocumentMapped() 242 | { 243 | return Definition::DOCUMENT == $this->mappedType; 244 | } 245 | 246 | /** 247 | * Check if the embedded definition handle many documents 248 | * 249 | * @return boolean 250 | */ 251 | public function isCollectionMapped() 252 | { 253 | return Definition::COLLECTION == $this->mappedType; 254 | } 255 | 256 | /** 257 | * Check whether a type is natively supported by the MongoDB php extension 258 | * 259 | * @return boolean 260 | */ 261 | public function isNativeMapped() 262 | { 263 | return $this->isMapped() && isset(static::$nativeClasses[$this->mappedClass]); 264 | } 265 | 266 | /** 267 | * Check whether the embedded definition is mapped to an "user" class 268 | * 269 | * @return boolean 270 | */ 271 | public function isUserMapped() 272 | { 273 | return $this->isMapped() && !isset(static::$nativeClasses[$this->mappedClass]); 274 | } 275 | 276 | /** 277 | * Import an array of metadata 278 | * 279 | * @param array $metadata 280 | * 281 | * @return void 282 | */ 283 | private function fromArray(array $metadata) 284 | { 285 | // @TODO Throw exception if mandatory parameters Attribute & Key are missing !! 286 | 287 | $defaults = array('type' => 'mixed', 'mappedClass' => null); 288 | $data = array_merge($defaults, $metadata); 289 | 290 | $type = $data['type']; 291 | $mappedClass = $data['mappedClass']; 292 | $isSupported = isset(static::$supportedTypes[$type]); 293 | 294 | if ($isSupported && 'composite' === static::$supportedTypes[$type] 295 | && !empty($mappedClass)) { 296 | // Embedded collection: type => array, mappedClass => FQDN 297 | 298 | if (!static::isValidNamespace($mappedClass)) { 299 | throw new \InvalidArgumentException(sprintf('Mapped class "%s" is not a valid FQDN', $mappedClass)); 300 | } 301 | 302 | // Set the mapping type & prepend a \ on the mapped FQDN 303 | $this->mappedType = Definition::COLLECTION; 304 | $this->mappedClass = (strpos($mappedClass, '\\') === 0) ? $mappedClass : '\\'.$mappedClass; 305 | 306 | } elseif (!$isSupported) { 307 | // Embedded document: type => FQDN 308 | 309 | if (!static::isValidNamespace($type)) { 310 | throw new \InvalidArgumentException(sprintf('User type "%s" is not a valid FQDN', $type)); 311 | } 312 | 313 | // Set the mapping type & prepend a \ on the mapped FQDN 314 | $this->mappedType = Definition::DOCUMENT; 315 | $this->mappedClass = $type = (strpos($type, '\\') === 0) ? $type : '\\'.$type; 316 | } 317 | 318 | $this->type = $type; 319 | $this->attribute = $data['attribute']; 320 | $this->key = $data['key']; 321 | $this->accessor = $data['accessor']; 322 | $this->mutator = $data['mutator']; 323 | } 324 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Generator/AbstractGenerator.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder\Generator; 16 | 17 | use Boomgo\Builder\MapBuilder; 18 | use Symfony\Component\Finder\Finder; 19 | use TwigGenerator\Builder\Generator as TwigGenerator; 20 | 21 | /** 22 | * AbstractGenerator 23 | * 24 | * @author David Guyon 25 | */ 26 | abstract class AbstractGenerator implements GeneratorInterface 27 | { 28 | /** 29 | * @var Boomgo\Builder\MapBuilder 30 | */ 31 | private $mapBuilder; 32 | 33 | /** 34 | * @var TwigGenerator\Builder\Generator 35 | */ 36 | private $twigGenerator; 37 | 38 | /** 39 | * Constructor defines MapBuilder & TwigGenerator instance 40 | * 41 | * @param MapBuilder $mapBuilder 42 | * @param TwigGenerator $twigGenerator 43 | */ 44 | public function __construct(MapBuilder $mapBuilder, TwigGenerator $twigGenerator) 45 | { 46 | $this->setMapBuilder($mapBuilder); 47 | $this->setTwigGenerator($twigGenerator); 48 | } 49 | 50 | /** 51 | * Define the map builder instance 52 | * 53 | * @param MapBuilder $mapBuilder 54 | */ 55 | public function setMapBuilder(MapBuilder $mapBuilder) 56 | { 57 | $this->mapBuilder = $mapBuilder; 58 | } 59 | 60 | /** 61 | * Return the map builder instance 62 | * 63 | * @return MapBuilder 64 | */ 65 | public function getMapBuilder() 66 | { 67 | return $this->mapBuilder; 68 | } 69 | 70 | /** 71 | * Define the twig generator instance 72 | * 73 | * @param TwigGenerator $twigGenerator 74 | */ 75 | public function setTwigGenerator(TwigGenerator $twigGenerator) 76 | { 77 | $this->twigGenerator = $twigGenerator; 78 | $this->twigGenerator->setTemplateDirs(array(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Templates')); 79 | $this->twigGenerator->setMustOverwriteIfExists(true); 80 | } 81 | 82 | /** 83 | * Return the twig generator instance 84 | * 85 | * @return TwigGenerator 86 | */ 87 | public function getTwigGenerator() 88 | { 89 | return $this->twigGenerator; 90 | } 91 | 92 | /** 93 | * Return a collection of files 94 | * 95 | * @param mixed $resources Absolute file or directory path or an array of both 96 | * @param string $extension File extension to load/filter with the prefixed dot (.php, .yml) 97 | * 98 | * @throws InvalidArgumentException If resources aren't absolute dir or file path 99 | * 100 | * @return array 101 | */ 102 | public function load($resources, $extension = '') 103 | { 104 | $finder = new Finder(); 105 | $collection = array(); 106 | 107 | if (is_array($resources)) { 108 | foreach ($resources as $resource) { 109 | $subcollection = array(); 110 | $subcollection = $this->load($resource); 111 | $collection = array_merge($collection, $subcollection); 112 | } 113 | } elseif (is_dir($resources)) { 114 | $files = $finder->files()->name('*'.$extension)->in($resources); 115 | foreach ($files as $file) { 116 | $collection[] = realpath($file->getPathName()); 117 | } 118 | } elseif (is_file($resources)) { 119 | $collection = array(realpath($resources)); 120 | } else { 121 | throw new \InvalidArgumentException('Argument must be an absolute directory or a file path or both in an array'); 122 | } 123 | 124 | return $collection; 125 | } 126 | 127 | /** 128 | * Callback to change default state of MapBuilder or TwigGenerator 129 | */ 130 | abstract protected function initialize(); 131 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Generator/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder\Generator; 16 | 17 | /** 18 | * GeneratorInterface 19 | * 20 | * @author David Guyon 21 | */ 22 | interface GeneratorInterface 23 | { 24 | /** 25 | * File generation process 26 | * 27 | * @param mixed $sources Mapping source(s) directory 28 | * @param string $generatedNamespace Namespace term for generated class (ex: Document, Model) 29 | * @param string $mappersNamespace Base mappers namespace (ex: Mapper, Mapping) 30 | * @param string $generatedDirectory Directory where classes are generated 31 | * 32 | * @return bool 33 | */ 34 | public function generate($sources, $generatedNamespace, $mappersNamespace, $generatedDirectory); 35 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Generator/MapperGenerator.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder\Generator; 16 | 17 | use Boomgo\Builder\TwigMapperBuilder; 18 | 19 | /** 20 | * MapperGenerator 21 | * 22 | * @author Ludovic Fleury 23 | * @author David Guyon 24 | */ 25 | class MapperGenerator extends AbstractGenerator 26 | { 27 | /** 28 | * Initialize default instance state 29 | */ 30 | protected function initialize() 31 | { 32 | $this->getTwigGenerator()->setVariables(array( 33 | 'extends' => 'MapperProvider', 34 | 'implements' => 'MapperInterface' 35 | )); 36 | } 37 | 38 | /** 39 | * Generate mappers 40 | * 41 | * The base models & mappers namespace are just the "namespace fragment" 42 | * not the full namespace part, i.e. "Document", "Mapper" 43 | * -"Document" & "Mapper": Project\Domain\Document => Project\Domain\Mapper 44 | * -"Document" & "Document\Mapper": Project\Domain\Document => Project\Domain\Document\Mapper 45 | * 46 | * The Base models directory & base models namespace must match PSR-O. 47 | * This means: base models namespace fragment must match the end of your base model directory. 48 | * - "Document" => "/path/to/your/Project/Document" 49 | * - "Domain\SubDomain\Model" => "/path/to/your/Domain/SubDomain/Model" 50 | * 51 | * The generator will write aside of your Document folder/namespace. If you want to change this 52 | * behavior, you just have to customize the base mapper namespace: "Document\Mapper" 53 | * 54 | * @param string $sources Mapping source directory 55 | * @param string $baseModelsNamespace Base models namespace (Document, Model) 56 | * @param string $baseMappersNamespace Base mappers namespace (Mapper, Mapping) 57 | * @param string $baseModelsDirectory Base models directory 58 | * 59 | * @return bool 60 | */ 61 | public function generate($sources, $baseModelsNamespace, $baseMappersNamespace, $baseModelsDirectory) 62 | { 63 | // Explicit call for MapperGenerator requirements 64 | $this->initialize(); 65 | 66 | $baseModelsNamespace = trim($baseModelsNamespace, '\\'); 67 | $baseMappersNamespace = trim($baseMappersNamespace, '\\'); 68 | $baseModelsDirectory = rtrim($baseModelsDirectory, DIRECTORY_SEPARATOR); 69 | 70 | $part = str_replace('\\', DIRECTORY_SEPARATOR, $baseModelsNamespace); 71 | 72 | if (str_replace($part, '', $baseModelsDirectory).$part !== $baseModelsDirectory) { 73 | throw new \InvalidArgumentException(sprintf('Boomgo support only PSR-O structure, your namespace "%s" doesn\'t reflect your directory structure "%s"', $baseModelsNamespace, $baseModelsDirectory)); 74 | } 75 | 76 | $files = $this->load($sources, '.'.$this->getMapBuilder()->getParser()->getExtension()); 77 | $maps = $this->getMapBuilder()->build($files); 78 | 79 | foreach ($maps as $map) { 80 | $modelClassName = $map->getClassName(); 81 | $modelNamespace = trim($map->getNamespace(), '\\'); 82 | 83 | if (substr_count($modelNamespace, $baseModelsNamespace) == 0) { 84 | throw new \RuntimeException(sprintf('The Document map "%s" doesn\'t include the document base namespace "%s"', $map->getClass(), $baseModelsNamespace)); 85 | } 86 | 87 | $modelExtraNamespace = str_replace($baseModelsNamespace, '', strstr($modelNamespace, $baseModelsNamespace)); 88 | $mapperDirectory = str_replace(str_replace('\\', DIRECTORY_SEPARATOR, $baseModelsNamespace), str_replace('\\', DIRECTORY_SEPARATOR, $baseMappersNamespace), str_replace('\\', DIRECTORY_SEPARATOR, $baseModelsDirectory.$modelExtraNamespace)); 89 | $mapperClassName = $modelClassName.'Mapper'; 90 | $mapperFileName = $mapperClassName.'.php'; 91 | 92 | $twigMapperBuilder = new TwigMapperBuilder(); 93 | 94 | $this->getTwigGenerator()->addBuilder($twigMapperBuilder); 95 | 96 | $twigMapperBuilder->setOutputName($mapperFileName); 97 | $twigMapperBuilder->setVariable('mappersNamespace', $baseMappersNamespace); 98 | $twigMapperBuilder->setVariable('modelsNamespace', $baseModelsNamespace); 99 | $twigMapperBuilder->setVariable('namespace', str_replace($baseModelsNamespace, $baseMappersNamespace, $modelNamespace)); 100 | $twigMapperBuilder->setVariable('className', $mapperClassName); 101 | $twigMapperBuilder->setVariable('imports', array($modelNamespace)); 102 | $twigMapperBuilder->setVariable('map', $map); 103 | 104 | $this->getTwigGenerator()->writeOnDisk($mapperDirectory); 105 | } 106 | 107 | return true; 108 | } 109 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Map.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder; 16 | 17 | /** 18 | * Map 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | class Map 23 | { 24 | /** 25 | * @var string The mapped FQDN 26 | */ 27 | private $class; 28 | 29 | /** 30 | * @var array Php attributes name indexed by MongoDB keys name 31 | */ 32 | private $mongoIndex; 33 | 34 | /** 35 | * @var array Definitions indexed by "PHP attributes" 36 | */ 37 | private $definitions; 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param string $class The mapped FQDN 43 | */ 44 | public function __construct($class) 45 | { 46 | $this->class = (strpos($class, '\\') === 0) ? $class : '\\'.$class; 47 | $this->mongoIndex = array(); 48 | $this->definitions = array(); 49 | $this->dependencies = array(); 50 | } 51 | 52 | /** 53 | * Return the FQDN of the mapped class 54 | * 55 | * @return string 56 | */ 57 | public function getClass() 58 | { 59 | return $this->class; 60 | } 61 | 62 | /** 63 | * Return the mapped short class name 64 | * 65 | * @return string 66 | */ 67 | public function getClassName() 68 | { 69 | $array = explode('\\', $this->class); 70 | 71 | return $array[count($array)-1]; 72 | } 73 | 74 | /** 75 | * Return the mapped namespace without the short class name 76 | * 77 | * @return string 78 | */ 79 | public function getNamespace() 80 | { 81 | $array = explode('\\', $this->class); 82 | unset($array[count($array)-1]); 83 | 84 | return implode('\\', $array); 85 | } 86 | 87 | /** 88 | * Return the mongo indexed map 89 | * 90 | * Array keys are mongo keys & array values are php attributes 91 | * 92 | * @example array('mongoKey' => 'phpAttribute'); 93 | * 94 | * @return array 95 | */ 96 | public function getMongoIndex() 97 | { 98 | return $this->mongoIndex; 99 | } 100 | 101 | /** 102 | * Return an array of definitions indexed by php attribute name 103 | * 104 | * @return array 105 | */ 106 | public function getDefinitions() 107 | { 108 | return $this->definitions; 109 | } 110 | 111 | /** 112 | * Add a definition 113 | * 114 | * @param Definition $definition 115 | */ 116 | public function addDefinition(Definition $definition) 117 | { 118 | $attribute = $definition->getAttribute(); 119 | $key = $definition->getKey(); 120 | 121 | $this->mongoIndex[$key] = $attribute; 122 | 123 | $this->definitions[$attribute] = $definition; 124 | } 125 | 126 | /** 127 | * Check whether a definition exists 128 | * 129 | * @param string $identifier Php attribute name or Document key name 130 | * 131 | * @return boolean 132 | */ 133 | public function hasDefinition($identifier) 134 | { 135 | return isset($this->definitions[$identifier]) || isset($this->mongoIndex[$identifier]); 136 | } 137 | 138 | /** 139 | * Return a definition 140 | * 141 | * @param string $identifier Php attribute name or Document key name 142 | * 143 | * @return mixed null|Definition 144 | */ 145 | public function getDefinition($identifier) 146 | { 147 | // Identifier is a php attribute 148 | if (isset($this->definitions[$identifier])) { 149 | return $this->definitions[$identifier]; 150 | } 151 | 152 | // Identifier is a MongoDB Key 153 | if (isset($this->mongoIndex[$identifier])) { 154 | return $this->definitions[$this->mongoIndex[$identifier]]; 155 | } 156 | 157 | return null; 158 | } 159 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/MapBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder; 16 | 17 | use Boomgo\Formatter\FormatterInterface; 18 | use Boomgo\Parser\ParserInterface; 19 | 20 | /** 21 | * MapBuilder 22 | * 23 | * @author Ludovic Fleury 24 | * @author David Guyon 25 | */ 26 | class MapBuilder 27 | { 28 | /** 29 | * @var Boomgo\Parser\ParserInterface 30 | */ 31 | protected $parser; 32 | 33 | /** 34 | * @var Boomgo\Formatter\FormatterInterface 35 | */ 36 | protected $formatter; 37 | 38 | /** 39 | * @var string 40 | */ 41 | protected $mapClassName; 42 | 43 | /** 44 | * @var string 45 | */ 46 | protected $definitionClassName; 47 | 48 | /** 49 | * Constructor defines the Parser & Formatter 50 | * 51 | * @param ParserInterface $parser 52 | * @param FormatterInterface $formatter 53 | */ 54 | public function __construct(ParserInterface $parser, FormatterInterface $formatter) 55 | { 56 | $this->setParser($parser); 57 | $this->setFormatter($formatter); 58 | $this->mapClassName = 'Boomgo\\Builder\\Map'; 59 | $this->definitionClassName = 'Boomgo\\Builder\\Definition'; 60 | } 61 | 62 | /** 63 | * Define the parser instance 64 | * 65 | * @param ParserInterface $parser 66 | */ 67 | public function setParser(ParserInterface $parser) 68 | { 69 | $this->parser = $parser; 70 | } 71 | 72 | /** 73 | * Return parser instance 74 | * 75 | * @return ParserInterface 76 | */ 77 | public function getParser() 78 | { 79 | return $this->parser; 80 | } 81 | 82 | /** 83 | * Define the key/attribute formatter instance 84 | * 85 | * @param FormatterInterface $formatter 86 | */ 87 | public function setFormatter(FormatterInterface $formatter) 88 | { 89 | $this->formatter = $formatter; 90 | } 91 | 92 | /** 93 | * Return the key/attribute formatter instance 94 | * 95 | * @return FormatterInterface 96 | */ 97 | public function getFormatter() 98 | { 99 | return $this->formatter; 100 | } 101 | 102 | /** 103 | * Define the map classname 104 | * 105 | * @param string $mapClassName 106 | */ 107 | public function setMapClassName($mapClassName) 108 | { 109 | $this->mapClassName = $mapClassName; 110 | } 111 | 112 | /** 113 | * Return the map classname 114 | * 115 | * @return string 116 | */ 117 | public function getMapClassName() 118 | { 119 | return $this->mapClassName; 120 | } 121 | 122 | /** 123 | * Define the definition classname 124 | * 125 | * @param string $definitionClassName 126 | */ 127 | public function setDefinitionClassName($definitionClassName) 128 | { 129 | $this->definitionClassName = $definitionClassName; 130 | } 131 | 132 | /** 133 | * Return the definition classname 134 | * 135 | * @return string 136 | */ 137 | public function getDefinitionClassName() 138 | { 139 | return $this->definitionClassName; 140 | } 141 | 142 | /** 143 | * Build Map(s) for an array of file 144 | * 145 | * @param array $files 146 | * 147 | * @return array $processed 148 | */ 149 | public function build(array $files) 150 | { 151 | $processed = array(); 152 | 153 | foreach ($files as $file) { 154 | if ($this->parser->supports($file)) { 155 | $metadata = $this->parser->parse($file); 156 | $map = $this->buildMap($metadata); 157 | 158 | $processed[$map->getClass()] = $map; 159 | } 160 | } 161 | 162 | return $processed; 163 | } 164 | 165 | /** 166 | * Build a Map 167 | * 168 | * @param array $metadata 169 | * 170 | * @return Map 171 | */ 172 | protected function buildMap(array $metadata) 173 | { 174 | $className = $this->getMapClassName(); 175 | $map = new $className($metadata['class']); 176 | 177 | foreach ($metadata['definitions'] as $metadataDefinition) { 178 | $definition = $this->buildDefinition($metadataDefinition); 179 | $map->addDefinition($definition); 180 | } 181 | 182 | return $map; 183 | } 184 | 185 | /** 186 | * Build a Definition 187 | * 188 | * @param array $metadata 189 | * 190 | * @return Definition 191 | */ 192 | protected function buildDefinition(array $metadata) 193 | { 194 | if (!isset($metadata['attribute']) && !isset($metadata['key'])) { 195 | throw new \RuntimeException('Invalid metadata should provide an attribute or a key'); 196 | } 197 | 198 | // @TODO Rethink this hacky method cause I hate annotation ? 199 | if (!isset($metadata['key'])) { 200 | $metadata['key'] = $this->formatter->toMongoKey($metadata['attribute']); 201 | } elseif (!isset($metadata['attribute'])) { 202 | $metadata['attribute'] = $this->formatter->toPhpAttribute($metadata['key']); 203 | } 204 | 205 | $metadata['accessor'] = $this->formatter->getPhpAccessor($metadata['attribute']); 206 | $metadata['mutator'] = $this->formatter->getPhpMutator($metadata['attribute']); 207 | 208 | $className = $this->getDefinitionClassName(); 209 | $definition = new $className($metadata); 210 | 211 | return $definition; 212 | } 213 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/Templates/Mapper.php.twig: -------------------------------------------------------------------------------- 1 | hydrate($object, $data); 34 | 35 | return $object; 36 | } 37 | 38 | /** 39 | * Serialize an object to a MongoDB array 40 | * 41 | * Return an Mongo-able array from a Php object 42 | * 43 | * @param {{ map.class }} $object 44 | * 45 | * @return array 46 | */ 47 | public function serialize($object) 48 | { 49 | if (!$object instanceof {{ map.getClass }}) { 50 | throw new \InvalidArgumentException('Serialize expect an instance of "{{ map.getClass }}"'); 51 | } 52 | 53 | $data = array(); 54 | {% for definition in map.definitions %} 55 | 56 | $value = $object->{{ definition.accessor }}(); 57 | if (null != $value && !empty($value)) { 58 | 59 | {%- if definition.isUserMapped %} 60 | 61 | $mapper = new {{ definition.getMappedClass|replace({(modelsNamespace): mappersNamespace}) }}Mapper(); 62 | 63 | {%- if definition.isDocumentMapped %} 64 | 65 | $data['{{ definition.key }}'] = $mapper->serialize($value); 66 | 67 | {%- elseif definition.isCollectionMapped %} 68 | 69 | $data['{{ definition.key }}'] = $this->serializeEmbeddedCollection($mapper, $value); 70 | 71 | {%- endif %} 72 | 73 | {%- elseif definition.isComposite %} 74 | 75 | $data['{{ definition.key }}'] = $this->normalize($value); 76 | 77 | {%- else %} 78 | 79 | $data['{{ definition.key }}'] = $value; 80 | 81 | {%- endif %} 82 | 83 | } 84 | {% endfor %} 85 | 86 | return $data; 87 | } 88 | 89 | /** 90 | * Hydrate an object from a MongoDb array 91 | * 92 | * @param {{ map.getClass }} $object 93 | * @param array $data MongoDB result array (a document) 94 | */ 95 | public function hydrate($object, array $data) 96 | { 97 | if (!$object instanceof {{ map.getClass }}) { 98 | throw new \InvalidArgumentException('Serialize expect an instance of "{{ map.getClass }}"'); 99 | } 100 | {% for definition in map.definitions %} 101 | 102 | if (isset($data['{{ definition.key }}'])) { 103 | 104 | {%- if definition.isUserMapped %} 105 | 106 | $mapper = new {{ definition.getMappedClass|replace({(modelsNamespace): mappersNamespace}) }}Mapper(); 107 | 108 | {%- if definition.isDocumentMapped %} 109 | 110 | $embeddedObject = $mapper->unserialize($data['{{ definition.key }}']); 111 | $object->{{ definition.mutator }}($embeddedObject); 112 | 113 | {%- elseif definition.isCollectionMapped %} 114 | 115 | $embeddedCollection = $this->unserializeEmbeddedCollection($mapper, $data['{{ definition.key }}']); 116 | $object->{{ definition.mutator }}($embeddedCollection); 117 | 118 | {%- endif %} 119 | 120 | {%- else %} 121 | 122 | $object->{{ definition.mutator }}($data['{{ definition.key }}']); 123 | 124 | {%- endif %} 125 | 126 | } 127 | {% endfor %} 128 | 129 | } 130 | } -------------------------------------------------------------------------------- /src/Boomgo/Builder/TwigMapperBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Builder; 16 | 17 | use TwigGenerator\Builder\BaseBuilder; 18 | 19 | /** 20 | * MapperBuilder 21 | * 22 | * @author Ludovic Fleury 23 | * @author David Guyon 24 | */ 25 | class TwigMapperBuilder extends BaseBuilder 26 | { 27 | /** 28 | * {@inheritdoc} 29 | * 30 | * @return string 31 | */ 32 | public function getDefaultTemplateName() 33 | { 34 | return 'Mapper'.self::TWIG_EXTENSION; 35 | } 36 | } -------------------------------------------------------------------------------- /src/Boomgo/Cache/ArrayCache.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Cache; 16 | 17 | /** 18 | * Array cache 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | class ArrayCache implements CacheInterface 23 | { 24 | /** 25 | * @var array 26 | */ 27 | private $data; 28 | 29 | /** 30 | * Constructor initializes empty data array 31 | */ 32 | public function __construct() 33 | { 34 | $this->data = array(); 35 | } 36 | 37 | /** 38 | * Check if a cached entry exists 39 | * 40 | * @param string $identifier Unique cache identifier 41 | * 42 | * @return boolean 43 | */ 44 | public function has($identifier) 45 | { 46 | return isset($this->data[$identifier]); 47 | } 48 | 49 | /** 50 | * Return a cached data 51 | * 52 | * @param string $identifier Unique cache identifier 53 | * 54 | * @return mixed 55 | */ 56 | public function get($identifier) 57 | { 58 | return ($this->has($identifier)) ? $this->data[$identifier] : null; 59 | } 60 | 61 | /** 62 | * Cache data 63 | * 64 | * @param string $identifier Unique cache identifier 65 | * @param mixed $data Data to be cached 66 | * @param integer $ttl Time To Live in second 67 | */ 68 | public function add($identifier, $data, $ttl = 0) 69 | { 70 | $this->data[$identifier] = $data; 71 | } 72 | 73 | /** 74 | * Delete a cached entry 75 | * 76 | * @param string $identifier Unique cache identifier 77 | */ 78 | public function remove($identifier) 79 | { 80 | unset($this->data[$identifier]); 81 | } 82 | 83 | /** 84 | * Clear all cache 85 | */ 86 | public function clear() 87 | { 88 | $this->data = array(); 89 | } 90 | } -------------------------------------------------------------------------------- /src/Boomgo/Cache/CacheInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Cache; 16 | 17 | /** 18 | * Cache interface 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | interface CacheInterface 23 | { 24 | /** 25 | * Check if a cached entry exists 26 | * 27 | * @param string $identifier Unique cache identifier 28 | * 29 | * @return boolean 30 | */ 31 | public function has($identifier); 32 | 33 | /** 34 | * Return a cached data 35 | * 36 | * @param string $identifier Unique cache identifier 37 | * 38 | * @return mixed 39 | */ 40 | public function get($identifier); 41 | 42 | /** 43 | * Cache data 44 | * 45 | * @param string $identifier Unique cache identifier 46 | * @param mixed $data Data to be cached 47 | * @param integer $ttl Time To Live in second 48 | */ 49 | public function add($identifier, $data, $ttl = 0); 50 | 51 | /** 52 | * Delete a cached entry 53 | * 54 | * @param string $identifier Unique cache identifier 55 | */ 56 | public function remove($identifier); 57 | 58 | /** 59 | * Clear all cache 60 | */ 61 | public function clear(); 62 | } -------------------------------------------------------------------------------- /src/Boomgo/Cache/NoCache.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Cache; 16 | 17 | /** 18 | * No cache 19 | * 20 | * Dummy cache implementation usefull for dev environment. 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | class NoCache implements CacheInterface 25 | { 26 | /** 27 | * {@inheritdoc} 28 | * 29 | * @param string $identifier Unique cache identifier 30 | * 31 | * @return boolean 32 | */ 33 | public function has($identifier) 34 | { 35 | return false; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | * 41 | * @param string $identifier Unique cache identifier 42 | * 43 | * @return mixed 44 | */ 45 | public function get($identifier) 46 | { 47 | return null; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | * 53 | * @param string $identifier Unique cache identifier 54 | * @param mixed $data Data to be cached 55 | * @param integer $ttl Time To Live in second 56 | */ 57 | public function add($identifier, $data, $ttl = 0) 58 | { 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | * 64 | * @param string $identifier Unique cache identifier 65 | */ 66 | public function remove($identifier) 67 | { 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function clear() 74 | { 75 | } 76 | } -------------------------------------------------------------------------------- /src/Boomgo/Console/Command/MapperGeneratorCommand.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Console\Command; 16 | 17 | use Symfony\Component\Console\Command\Command, 18 | Symfony\Component\Console\Input\InputInterface, 19 | Symfony\Component\Console\Input\InputArgument, 20 | Symfony\Component\Console\Input\InputOption, 21 | Symfony\Component\Console\Output\OutputInterface; 22 | use Boomgo\Builder\MapBuilder, 23 | Boomgo\Builder\Generator\MapperGenerator; 24 | use TwigGenerator\Builder\Generator as TwigGenerator; 25 | 26 | /** 27 | * Mapper Generator Command 28 | * 29 | * @author Ludovic Fleury 30 | */ 31 | class MapperGeneratorCommand extends Command 32 | { 33 | /** 34 | * Constructor 35 | * 36 | * @param string $name Command name 37 | */ 38 | public function __construct($name = null) 39 | { 40 | parent::__construct($name); 41 | 42 | $this->setDescription('Mapper generator command'); 43 | $this->setHelp('generate:mappers Generate mappers'); 44 | $this->addArgument('mapping-directory', InputArgument::REQUIRED, 'Mapping sources absolute directory path'); 45 | $this->addArgument('models-directory', InputArgument::OPTIONAL, 'Base model/document directory', null); 46 | $this->addOption('models-namespace', null, InputOption::VALUE_OPTIONAL, 'Model/document namespace (i.e Document or Model)', 'Document'); 47 | $this->addOption('mappers-namespace', null, InputOption::VALUE_OPTIONAL, 'Mappers namespace, default "Mapper"', 'Mapper'); 48 | $this->addOption('parser', null, InputOption::VALUE_OPTIONAL, 'Mapping parser', 'annotation'); 49 | $this->addOption('formatter', null, InputOption::VALUE_OPTIONAL, 'Mapping formatter', 'CamelCase'); 50 | } 51 | 52 | /** 53 | * Execute the command 54 | * 55 | * @param InputInterface $input 56 | * @param OutputInterface $output 57 | */ 58 | protected function execute(InputInterface $input, OutputInterface $output) 59 | { 60 | $params = array(); 61 | $params = array_merge($input->getArguments(), $input->getOptions()); 62 | 63 | if (!is_dir($params['mapping-directory'])) { 64 | throw new \InvalidArgumentException('Invalid mapping sources directory'); 65 | } 66 | 67 | if (null == $params['models-directory']) { 68 | $params['models-directory'] = $params['mapping-directory']; 69 | } 70 | 71 | $parserClass = (strpos($params['parser'], '\\') === false) ? '\\Boomgo\\Parser\\'.ucfirst($params['parser']).'Parser' : $params['parser']; 72 | $formatterClass = (strpos($params['formatter'], '\\') === false) ? '\\Boomgo\\Formatter\\'.ucfirst($params['formatter']).'Formatter' : $params['formatter']; 73 | 74 | $formatter = new $formatterClass; 75 | $parser = new $parserClass; 76 | 77 | $mapBuilder = new MapBuilder($parser, $formatter); 78 | $twigGenerator = new TwigGenerator(); 79 | $mapperGenerator = new MapperGenerator($mapBuilder, $twigGenerator); 80 | 81 | $mapperGenerator->generate($params['mapping-directory'], $params['models-namespace'], $params['mappers-namespace'], $params['models-directory']); 82 | 83 | $output->writeln('Mappers have been generated'); 84 | } 85 | } -------------------------------------------------------------------------------- /src/Boomgo/Formatter/CamelCaseFormatter.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Formatter; 16 | 17 | /** 18 | * CamelCaseFormatter 19 | * 20 | * Formatter for Mongo key camelCase & PHP camelCase attribute. 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | class CamelCaseFormatter implements FormatterInterface 25 | { 26 | /** 27 | * {@inheritdoc} 28 | * Handle _id exception since mongoDB use underscored identifier 29 | * 30 | * @param string $string 31 | * 32 | * @return string $string 33 | */ 34 | public function toMongoKey($string) 35 | { 36 | return ($string == 'id') ? '_id' : $string; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | * Handle _id exception since mongoDB use underscored identifier 42 | * 43 | * @param string $string 44 | * 45 | * @return string $string 46 | */ 47 | public function toPhpAttribute($string) 48 | { 49 | return ($string == '_id') ? 'id' : $string; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | * 55 | * @param string $string A mongo key or a php attribute 56 | * @param string $type The php type 57 | * 58 | * @return string 59 | */ 60 | public function getPhpAccessor($string, $type = 'mixed') 61 | { 62 | $prefix = (($type == 'bool' || $type == 'boolean') ? 'is' : 'get'); 63 | 64 | return $prefix.ucfirst($string); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | * 70 | * @param string $string A mongo key or a php attribute 71 | * 72 | * @return string 73 | */ 74 | public function getPhpMutator($string) 75 | { 76 | return 'set'.ucfirst($string); 77 | } 78 | } -------------------------------------------------------------------------------- /src/Boomgo/Formatter/FormatterInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Formatter; 16 | 17 | /** 18 | * Formatter Interface 19 | * 20 | * Invoked to translate a mongo key name to a php attribute 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | interface FormatterInterface 25 | { 26 | /** 27 | * Format a mongo key to a php attribute 28 | * 29 | * @param string $mongoKey 30 | * 31 | * @return string 32 | */ 33 | public function toPhpAttribute($mongoKey); 34 | 35 | /** 36 | * Format a php attribute to a mongo key 37 | * 38 | * @param string $phpAttribute 39 | * 40 | * @return string 41 | */ 42 | public function toMongoKey($phpAttribute); 43 | 44 | /** 45 | * Get a php accessor name from a php attribute 46 | * 47 | * @param string $string The php attribute 48 | * @param string $type The php type 49 | * 50 | * @return string 51 | */ 52 | public function getPhpAccessor($string, $type = 'mixed'); 53 | 54 | /** 55 | * Get a php mutator name a php attribute 56 | * 57 | * @param string $string The php attribute 58 | * 59 | * @return string 60 | */ 61 | public function getPhpMutator($string); 62 | } 63 | -------------------------------------------------------------------------------- /src/Boomgo/Formatter/TransparentFormatter.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Formatter; 16 | 17 | /** 18 | * Transparent Formatter 19 | * 20 | * Provide FormatterInterface implementation when mongo key are identical to php attribute 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | class TransparentFormatter implements FormatterInterface 25 | { 26 | /** 27 | * Return the exact same php attribute 28 | * 29 | * @param string $phpAttribute 30 | * 31 | * @return string $mongoKey 32 | */ 33 | public function toMongoKey($phpAttribute) 34 | { 35 | return $phpAttribute; 36 | } 37 | 38 | /** 39 | * Return the exact same mongo key 40 | * 41 | * @param string $mongoKey 42 | * 43 | * @return string $mongoKey 44 | */ 45 | public function toPhpAttribute($mongoKey) 46 | { 47 | return $mongoKey; 48 | } 49 | 50 | /** 51 | * Return the php attribute prefixed with get or is 52 | * 53 | * @param string $string 54 | * @param string $type 55 | * 56 | * @return string 57 | */ 58 | public function getPhpAccessor($string, $type = 'mixed') 59 | { 60 | $prefix = (($type === 'bool' || $type === 'boolean') ? 'is' : 'get'); 61 | 62 | return $prefix.$string; 63 | } 64 | 65 | /** 66 | * Return the php attribute always prefixed with set 67 | * 68 | * @param string $string 69 | * 70 | * @return string 71 | */ 72 | public function getPhpMutator($string) 73 | { 74 | return 'set'.$string; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Boomgo/Formatter/Underscore2CamelFormatter.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Formatter; 16 | 17 | /** 18 | * Underscore2CamelFormatter 19 | * 20 | * Formatter for Mongo key underscore & PHP camelCase attribute. 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | class Underscore2CamelFormatter implements FormatterInterface 25 | { 26 | /** 27 | * Return an underscored mongo key from a php attribute 28 | * 29 | * Handle _id exception since mongoDB use underscored identifier 30 | * 31 | * @param string $phpAttribute A camelCase string 32 | * 33 | * @return string 34 | */ 35 | public function toMongoKey($phpAttribute) 36 | { 37 | $underscored = $this->underscore($phpAttribute); 38 | 39 | return ($phpAttribute == 'id') ? '_id' : $underscored ; 40 | } 41 | 42 | /** 43 | * Return a camelCase php attribute from a underscored mongo key 44 | * 45 | * @param string $mongoKey An underscored string 46 | * 47 | * @return string 48 | */ 49 | public function toPhpAttribute($mongoKey) 50 | { 51 | // will camelize in lower case (so it handles the _id exception) 52 | return $this->camelize($mongoKey); 53 | } 54 | 55 | /** 56 | * Return a php accessor for a php attribute 57 | * 58 | * @param string $string A php attribute 59 | * @param string $type The php type 60 | * 61 | * @return string 62 | */ 63 | public function getPhpAccessor($string, $type = 'mixed') 64 | { 65 | $prefix = (($type ==='bool' || $type === 'boolean') ? 'is' : 'get'); 66 | 67 | return $prefix.ucfirst($string); 68 | } 69 | 70 | /** 71 | * Return a php mutator for a php attribute 72 | * 73 | * @param string $string A php attribute 74 | * 75 | * @return string 76 | */ 77 | public function getPhpMutator($string) 78 | { 79 | return 'set'.ucfirst($string); 80 | } 81 | 82 | /** 83 | * Convert underscored string to lower|upper camelCase 84 | * 85 | * @param string $string An underscored string 86 | * @param bool $lower 87 | * 88 | * @return string 89 | * 90 | * @example my_great_key -> myGreatKey|MyGreatKey 91 | */ 92 | private function camelize($string, $lower = true) 93 | { 94 | $words = explode('_', strtolower($string)); 95 | 96 | $camelized = ''; 97 | 98 | foreach ($words as $word) { 99 | if (strpos($word, '_') === false) { 100 | $camelized .= ucfirst(trim($word)); 101 | } 102 | } 103 | 104 | return ($lower) ? lcfirst($camelized) : $camelized; 105 | } 106 | 107 | /** 108 | * Convert a camelCase string to an underscore string 109 | * 110 | * @param string $string A camelCase string 111 | * 112 | * @return string 113 | * 114 | * @example myGreatKey|MyGreatKey -> my_great_key 115 | */ 116 | private function underscore($string) 117 | { 118 | return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $string)); 119 | } 120 | } -------------------------------------------------------------------------------- /src/Boomgo/Mapper/MapperInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Mapper; 16 | 17 | use Boomgo\Map; 18 | 19 | /** 20 | * MapperInterface 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | interface MapperInterface 25 | { 26 | /** 27 | * Return an mongoable array from an object 28 | * 29 | * @param mixed $object 30 | * 31 | * @return array 32 | */ 33 | public function serialize($object); 34 | 35 | /** 36 | * Return an hydrated object from an MongoDB array 37 | * 38 | * @param array $array 39 | * 40 | * @return object 41 | */ 42 | public function unserialize(array $array); 43 | 44 | /** 45 | * Hydrate a PHP object from a MongoDB array 46 | * 47 | * @param mixed $object 48 | * @param array $array 49 | * 50 | * @return object 51 | */ 52 | public function hydrate($object, array $array); 53 | } -------------------------------------------------------------------------------- /src/Boomgo/Mapper/MapperProvider.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Mapper; 16 | 17 | /** 18 | * MapperProvider 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | abstract class MapperProvider 23 | { 24 | /** 25 | * Create an instance from a Reflected class 26 | * 27 | * @param ReflectionClass $reflectedClass 28 | * 29 | * @throws RuntimeException If constructor requires parameter 30 | * 31 | * @return mixed 32 | */ 33 | protected function createInstance(\ReflectionClass $reflectedClass) 34 | { 35 | $constructor = $reflectedClass->getConstructor(); 36 | 37 | if ($constructor && $constructor->getNumberOfRequiredParameters() > 0) { 38 | throw new \RuntimeException('Unable to hydrate an object requiring constructor param'); 39 | } 40 | 41 | return $reflectedClass->newInstance(); 42 | } 43 | 44 | /** 45 | * Normalize php data for mongo 46 | * 47 | * This code chunk was inspired by the Symfony framework 48 | * and is subject to the MIT license. Please see the LICENCE 49 | * at https://github.com/symfony/symfony 50 | * 51 | * (c) Fabien Potencier 52 | * Author Nils Adermann 53 | * 54 | * @param mixed $data 55 | * 56 | * @return mixed 57 | */ 58 | protected function normalize($data) 59 | { 60 | if (null === $data || is_scalar($data)) { 61 | return $data; 62 | } 63 | 64 | if (is_array($data)) { 65 | foreach ($data as $key => $val) { 66 | $data[$key] = $this->normalize($val); 67 | } 68 | 69 | return $data; 70 | } 71 | 72 | throw new \RuntimeException('An unexpected value could not be normalized: '.var_export($data, true)); 73 | } 74 | 75 | /** 76 | * Serialize an embedded collection 77 | * 78 | * Return a collection of hydrated objects 79 | * 80 | * @param MapperInterface $mapper 81 | * @param array $collection 82 | * 83 | * @return array 84 | */ 85 | protected function serializeEmbeddedCollection(MapperInterface $mapper, array $collection) 86 | { 87 | $data = array(); 88 | foreach ($collection as $object) { 89 | $data[] = $mapper->serialize($object); 90 | } 91 | 92 | return $data; 93 | } 94 | 95 | /** 96 | * Unserialize an embedded collection 97 | * 98 | * Return a collection of serialized objects (arrays) 99 | * 100 | * @param MapperInterface $mapper 101 | * @param array $data 102 | * 103 | * @return array 104 | */ 105 | protected function unserializeEmbeddedCollection(MapperInterface $mapper, array $data) 106 | { 107 | $collection = array(); 108 | foreach ($data as $document) { 109 | $collection[] = $mapper->unserialize($document); 110 | } 111 | 112 | return $collection; 113 | } 114 | } -------------------------------------------------------------------------------- /src/Boomgo/Mapper/SimpleMapper.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Mapper; 16 | 17 | use Boomgo\Formatter\FormatterInterface; 18 | 19 | /** 20 | * SimpleMapper 21 | * 22 | * Live mapper allowing MongoDB schemaless feature 23 | * Rely on dynamic object analyze (do not use a Map definition) 24 | * 25 | * @author Ludovic Fleury 26 | */ 27 | class SimpleMapper extends MapperProvider 28 | { 29 | /** 30 | * @var boolean 31 | */ 32 | private $schemaLess; 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param FormatterInterface $formatter A formatter 38 | * @param boolean $schemaLess True to enable schemaless 39 | */ 40 | public function __construct(FormatterInterface $formatter, $schemaLess = true) 41 | { 42 | $this->setFormatter($formatter); 43 | $this->schemaLess = $schemaLess; 44 | } 45 | 46 | /** 47 | * Define the formatter 48 | * 49 | * @param FormatterInterface $formatter 50 | */ 51 | public function setFormatter(FormatterInterface $formatter) 52 | { 53 | $this->formatter = $formatter; 54 | } 55 | 56 | /** 57 | * Return the formatter used 58 | * 59 | * @return FormatterInterface 60 | */ 61 | public function getFormatter() 62 | { 63 | return $this->formatter; 64 | } 65 | 66 | /** 67 | * Enable or disable schema less 68 | * 69 | * @param boolean $switch 70 | */ 71 | public function setSchemaLess($switch) 72 | { 73 | $this->schemaLess = $switch; 74 | } 75 | 76 | /** 77 | * Return whether schema less is enabled 78 | * 79 | * @return boolean 80 | */ 81 | public function isSchemaLess() 82 | { 83 | return $this->schemaLess; 84 | } 85 | 86 | /** 87 | * Convert an object to a mongoable array 88 | * 89 | * @param mixed $object 90 | * 91 | * @return array 92 | */ 93 | public function serialize($object) 94 | { 95 | if (!is_object($object)) { 96 | throw new \InvalidArgumentException('Argument must be an object'); 97 | } 98 | 99 | $reflectedObject = new \ReflectionObject($object); 100 | $reflectedProperties = $reflectedObject->getProperties(); 101 | 102 | $array = array(); 103 | 104 | foreach ($reflectedProperties as $property) { 105 | $value = $this->getValue($object, $property); 106 | 107 | if (null !== $value) { 108 | if (!is_scalar($value)) { 109 | $value = $this->normalize($value); 110 | } 111 | 112 | $array[$this->formatter->toMongoKey($property->getName())] = $value; 113 | } 114 | } 115 | 116 | return $array; 117 | } 118 | 119 | /** 120 | * Hydrate an object 121 | * 122 | * If schemaless is enabled, any data in the array 123 | * will be dynamically appended to the object 124 | * 125 | * @param mixed $object 126 | * @param array $array 127 | * 128 | * @return mixed 129 | */ 130 | public function hydrate($object, array $array) 131 | { 132 | $reflected = new \ReflectionClass($object); 133 | 134 | if (is_string($object)) { 135 | $object = $this->createInstance($reflected); 136 | } 137 | 138 | foreach ($array as $key => $value) { 139 | $attributeName = $this->formatter->toPhpAttribute($key); 140 | 141 | if ($reflected->hasProperty($attributeName)) { 142 | 143 | $property = $reflected->getProperty($attributeName); 144 | $this->setValue($object, $property, $value); 145 | } elseif ($this->isSchemaless()) { 146 | $object->$attributeName = $value; 147 | } 148 | } 149 | 150 | return $object; 151 | } 152 | 153 | /** 154 | * Return a value for an object property 155 | * 156 | * @param mixed $object 157 | * @param \ReflectionProperty $property 158 | * 159 | * @return mixed 160 | */ 161 | private function getValue($object, \ReflectionProperty $property) 162 | { 163 | $value = null; 164 | 165 | if ($property->isPublic()) { 166 | $value = $property->getValue($object); 167 | } else { 168 | // Try to use accessor if property is not public 169 | $accessorName = $this->formatter->getPhpAccessor($property->getName(), false); 170 | $reflectedObject = new \ReflectionObject($object); 171 | $reflectedMethod = $reflectedObject->getMethod($accessorName); 172 | 173 | if (null !== $reflectedMethod) { 174 | $value = $reflectedMethod->invoke($object); 175 | } 176 | } 177 | 178 | return $value; 179 | } 180 | 181 | /** 182 | * Define a value for an object property 183 | * 184 | * @param mixed $object 185 | * @param \ReflectionProperty $property 186 | * @param mixed $value 187 | */ 188 | private function setValue($object, \ReflectionProperty $property, $value) 189 | { 190 | if ($property->isPublic()) { 191 | $property->setValue($object, $value); 192 | } else { 193 | // Try to use mutator if property is not public 194 | $mutatorName = $this->formatter->getPhpMutator($property->getName(), false); 195 | $reflectedObject = new \ReflectionObject($object); 196 | $reflectedMethod = $reflectedObject->getMethod($mutatorName); 197 | 198 | if (null !== $reflectedMethod) { 199 | $reflectedMethod->invoke($object, $value); 200 | } 201 | } 202 | } 203 | } -------------------------------------------------------------------------------- /src/Boomgo/Parser/AnnotationParser.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Parser; 16 | 17 | use Boomgo\Formatter\FormatterInterface; 18 | 19 | /** 20 | * AnnotationParser 21 | * 22 | * @author Ludovic Fleury 23 | */ 24 | class AnnotationParser implements ParserInterface 25 | { 26 | /** 27 | * @var string Boomgo class annotation tag 28 | */ 29 | private $globalAnnotation; 30 | 31 | /** 32 | * @var string Boomgo property annotation tag 33 | */ 34 | private $localAnnotation; 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param string $globalTag 40 | * @param string $localTag 41 | */ 42 | public function __construct($globalTag = '@Boomgo', $localTag = '@Persistent') 43 | { 44 | $this->setGlobalAnnotation($globalTag); 45 | $this->setLocalAnnotation($localTag); 46 | } 47 | 48 | /** 49 | * Define the global annotation tag 50 | * 51 | * @param string $tag 52 | */ 53 | public function setGlobalAnnotation($tag) 54 | { 55 | if (!preg_match('#^@[a-zA-Z0-9]+$#', $tag)) { 56 | throw new \InvalidArgumentException('Boomgo annotation tag should start with "@" character'); 57 | } 58 | 59 | $this->globalAnnotation = $tag; 60 | } 61 | 62 | /** 63 | * Return the defined global annotation tag 64 | * 65 | * @return string 66 | */ 67 | public function getGlobalAnnotation() 68 | { 69 | return $this->globalAnnotation; 70 | } 71 | 72 | /** 73 | * Define the local annotation tag 74 | * 75 | * @param string $tag 76 | */ 77 | public function setLocalAnnotation($tag) 78 | { 79 | if (!preg_match('#^@[a-zA-Z0-9]+$#', $tag)) { 80 | throw new \InvalidArgumentException('Boomgo annotation tag should start with "@" character'); 81 | } 82 | 83 | $this->localAnnotation = $tag; 84 | } 85 | 86 | /** 87 | * Return the local annotation tag 88 | * 89 | * @return string 90 | */ 91 | public function getLocalAnnotation() 92 | { 93 | return $this->localAnnotation; 94 | } 95 | 96 | /** 97 | * ParserInterface implementation 98 | * 99 | * @see ParserInterface::getExtension() 100 | * 101 | * @return string 102 | */ 103 | public function getExtension() 104 | { 105 | return 'php'; 106 | } 107 | 108 | /** 109 | * ParserInterface implementation 110 | * 111 | * @param resource $resource 112 | * @param string $type 113 | * 114 | * @see ParserInterface::getExtension() 115 | * 116 | * @return boolean 117 | */ 118 | public function supports($resource, $type = null) 119 | { 120 | return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); 121 | } 122 | 123 | /** 124 | * ParserInterface implementation 125 | * Extract className and metadata properties 126 | * 127 | * @param string $filepath 128 | * 129 | * @see ParserInterface::parse() 130 | * 131 | * @return array 132 | */ 133 | public function parse($filepath) 134 | { 135 | $metadata = array(); 136 | 137 | $reflectedClass = $this->getReflection($filepath); 138 | $metadata['class'] = $reflectedClass->getName(); 139 | 140 | $propertiesMetadata = $this->processPropertiesParsing($reflectedClass); 141 | $metadata = array_merge($metadata, $propertiesMetadata); 142 | 143 | return $metadata; 144 | } 145 | 146 | /** 147 | * Extract the fully qualified namespace and return a ReflectionClass object 148 | * 149 | * @param string $filepath Path to the file to parse 150 | * 151 | * @return ReflectionClass 152 | */ 153 | protected function getReflection($filepath) 154 | { 155 | // Regexp instead of tokenizer because of the bad perf @link > https://gist.github.com/1886076 156 | if (!preg_match('#^namespace\s+(.+?);.*class\s+(\w+).+;$#sm', file_get_contents($filepath), $captured)) { 157 | throw new \RuntimeException('Unable to find namespace or class declaration'); 158 | } 159 | 160 | $fqcn = $captured[1].'\\'.$captured[2]; 161 | 162 | try { 163 | $reflectedClass = new \ReflectionClass($fqcn); 164 | } catch (\ReflectionException $exception) { 165 | $this->registerAutoload($fqcn, $filepath); 166 | $reflectedClass = new \ReflectionClass($fqcn); 167 | } 168 | 169 | return $reflectedClass; 170 | } 171 | 172 | /** 173 | * Parse class properties for metadata extraction if valid contains valid annotation local tag 174 | * 175 | * @param ReflectionClass $reflectedClass The document reflected object to parse 176 | * 177 | * @return array An array filled with Definition instance 178 | */ 179 | protected function processPropertiesParsing(\ReflectionClass $reflectedClass) 180 | { 181 | $metadata = array(); 182 | 183 | $reflectedProperties = $reflectedClass->getProperties(); 184 | 185 | foreach ($reflectedProperties as $reflectedProperty) { 186 | if ($this->isBoomgoProperty($reflectedProperty)) { 187 | $propertyMetadata = $this->parseMetadataProperty($reflectedProperty); 188 | $metadata['definitions'][$reflectedProperty->getName()] = $propertyMetadata; 189 | } 190 | } 191 | 192 | return $metadata; 193 | } 194 | 195 | /** 196 | * Check if an object property has to be processed by Boomgo 197 | * 198 | * @param ReflectionProperty $property the property to check 199 | * 200 | * @throws RuntimeException If annotation is malformed 201 | * 202 | * @return Boolean True if the property should be stored 203 | */ 204 | private function isBoomgoProperty(\ReflectionProperty $property) 205 | { 206 | $propertyName = $property->getName(); 207 | $className = $property->getDeclaringClass()->getName(); 208 | 209 | $annotationTag = substr_count($property->getDocComment(), $this->getLocalAnnotation()); 210 | if (0 < $annotationTag) { 211 | if (1 === $annotationTag) { 212 | return true; 213 | } 214 | 215 | throw new \RuntimeException(sprintf('Boomgo annotation tag should occur only once for "%s->%s"', $className, $propertyName)); 216 | } 217 | 218 | return false; 219 | } 220 | 221 | /** 222 | * Parse Boomgo metadata 223 | * 224 | * Extract metadata from the optional var tag 225 | * 226 | * @param \ReflectionProperty $property 227 | * 228 | * @return array 229 | */ 230 | private function parseMetadataProperty(\ReflectionProperty $property) 231 | { 232 | $metadata = array(); 233 | $tag = '@var'; 234 | $docComment = $property->getDocComment(); 235 | $occurence = (int) substr_count($docComment, $tag); 236 | 237 | if (1 < $occurence) { 238 | throw new \RuntimeException(sprintf('"@var" tag is not unique for "%s->%s"', $property->getDeclaringClass()->getName(), $property->getName())); 239 | } 240 | 241 | $metadata['attribute'] = $property->getName(); 242 | 243 | // Grep type and optional namespaces 244 | preg_match('#@var\h+([a-zA-Z0-9\\\\_]+)(?:\h+\[([a-zA-Z0-9\\\\\s,_]+)\]\h*|.*)\v#', $docComment, $captured); 245 | 246 | if (!empty($captured)) { 247 | 248 | // Format var metadata 249 | $metadata['type'] = $captured[1]; 250 | 251 | if (isset($captured[2])) { 252 | $metadata['mappedClass'] = trim($captured[2]); 253 | } 254 | } 255 | 256 | return $metadata; 257 | } 258 | 259 | /** 260 | * Fallback autoloader 261 | * 262 | * @param string $fqcn 263 | * @param string $path 264 | * 265 | * @return boolean True if the file has been loaded 266 | */ 267 | private function registerAutoload($fqcn, $path) 268 | { 269 | $namespace = str_replace(strrchr($fqcn, '\\'), '', $fqcn); 270 | $psr0 = str_replace('\\', DIRECTORY_SEPARATOR, $namespace); 271 | 272 | // A part of the namespace should match a part of the path (PSR-0 standard) 273 | if (substr_count($path, $psr0) == 0) { 274 | throw new \RuntimeException('Boomgo annotation parser support only PSR-0 project structure'); 275 | } 276 | 277 | $baseDirectory = str_replace($psr0, '', dirname($path)); 278 | $partNamespace = explode('\\', $namespace); 279 | $baseNamespace = $partNamespace[0]; 280 | 281 | spl_autoload_register(function($class) use ($baseNamespace, $baseDirectory) { 282 | if (0 === strpos($class, $baseNamespace)) { 283 | $path = $baseDirectory.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php'; 284 | if (!stream_resolve_include_path($path)) { 285 | return false; 286 | } 287 | require_once $path; 288 | 289 | return true; 290 | } 291 | }); 292 | } 293 | } -------------------------------------------------------------------------------- /src/Boomgo/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Parser; 16 | 17 | /** 18 | * ParserInterface 19 | * 20 | * @author Ludovic Fleury 21 | */ 22 | interface ParserInterface 23 | { 24 | /** 25 | * Return the extension supported without the prefixed dot 26 | * 27 | * @return string 28 | */ 29 | public function getExtension(); 30 | 31 | /** 32 | * Returns true if this class supports the given resource 33 | * 34 | * @param resource $resource 35 | * @param string $type 36 | * 37 | * @return boolean 38 | */ 39 | public function supports($resource, $type = null); 40 | 41 | /** 42 | * Extract and return an array of metadata from a resource 43 | * 44 | * @param string $filepath 45 | * 46 | * @return array 47 | */ 48 | public function parse($filepath); 49 | } -------------------------------------------------------------------------------- /src/Boomgo/console.php: -------------------------------------------------------------------------------- 1 | add(new \Boomgo\Console\Command\MapperGeneratorCommand('generate:mappers')); 7 | $application->run(); -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Builder/Definition.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Builder; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Builder as Src; 19 | 20 | /** 21 | * Definition tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class Definition extends Test 26 | { 27 | public function testIsValidNamespace() 28 | { 29 | // Should return true for valid FQDN 30 | $valid = array('\\Namespace', '\\Another\\NameSpace', 'Another\\Na_me\\Space'); 31 | foreach ($valid as $namespace) { 32 | $this->assert 33 | ->boolean(Src\Definition::isValidNamespace($namespace)) 34 | ->isTrue(); 35 | } 36 | 37 | // Should return false for invalid FQDN 38 | $invalid = array('notnamespace', 'Not \\Namespace', '\\Not Name\\space'); 39 | foreach ($invalid as $namespace) { 40 | $this->assert 41 | ->boolean(Src\Definition::isValidNamespace($namespace)) 42 | ->isFalse(); 43 | } 44 | } 45 | 46 | public function test__construct() 47 | { 48 | // Should throw an error if argument array (metadata) isn't provided 49 | $this->assert 50 | ->error(function() { 51 | new Src\Definition(); 52 | }) 53 | ->withType(E_RECOVERABLE_ERROR); 54 | 55 | // Should set the default type "mixed" 56 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 57 | $this->assert 58 | ->string($definition->getType()) 59 | ->isEqualTo('mixed'); 60 | 61 | // Should set a provided type 62 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'type' => 'string', 'accessor' => 'accessor', 'mutator' => 'mutator')); 63 | $this->assert 64 | ->string($definition->getType()) 65 | ->isEqualTo('string'); 66 | 67 | // Should ignore mappedClass when providing a supported non-mappable (pseudo) type 68 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'type' => 'string', 'accessor' => 'accessor', 'mutator' => 'mutator', 'mappedClass' => '\\User\\Namespace')); 69 | $this->assert 70 | ->string($definition->getType()) 71 | ->isEqualTo('string') 72 | ->variable($definition->getMappedType()) 73 | ->isNull() 74 | ->variable($definition->getMappedClass()) 75 | ->isNull(); 76 | 77 | // Should hanlde a custom type (FQDN): must be defined as a type and a mappedClass 78 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => '\\User\\Namespace\\Object')); 79 | $this->assert 80 | ->string($definition->getType()) 81 | ->isEqualTo('\\User\\Namespace\\Object') 82 | ->string($definition->getMappedType()) 83 | ->isEqualTo(Src\Definition::DOCUMENT) 84 | ->string($definition->getMappedClass()) 85 | ->isEqualTo('\\User\\Namespace\\Object'); 86 | 87 | // Should prepend a \ to a custom type (FQDN) 88 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 89 | $this->assert 90 | ->string($definition->getType()) 91 | ->isEqualTo('\\User\\Namespace\\Object') 92 | ->string($definition->getMappedClass()) 93 | ->isEqualTo('\\User\\Namespace\\Object'); 94 | 95 | // Should handle embedded collection of documents with type array and custom type as mappedClass 96 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => '\\Valid\\Namespace\\Object')); 97 | $this->assert 98 | ->string($definition->getType()) 99 | ->isEqualTo('array') 100 | ->string($definition->getMappedType()) 101 | ->isEqualTo(Src\Definition::COLLECTION) 102 | ->string($definition->getMappedClass()) 103 | ->isEqualTo('\\Valid\\Namespace\\Object'); 104 | 105 | // Should prepend a \ for embedded collection of documents 106 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'Valid\\Namespace\\Object')); 107 | $this->assert 108 | ->string($definition->getType()) 109 | ->isEqualTo('array') 110 | ->string($definition->getMappedClass()) 111 | ->isEqualTo('\\Valid\\Namespace\\Object'); 112 | 113 | // Should throw exception if type is not supported and isn't a valid FQDN 114 | $this->assert 115 | ->exception(function() { 116 | new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'invalid_FQDN')); 117 | }) 118 | ->isInstanceOf('InvalidArgumentException') 119 | ->hasMessage('User type "invalid_FQDN" is not a valid FQDN'); 120 | 121 | // Should throw exception if type is array and mappedClass isn't a valid FQDN 122 | $this->assert 123 | ->exception(function() { 124 | new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array','mappedClass' => 'invalid_FQDN')); 125 | }) 126 | ->isInstanceOf('InvalidArgumentException') 127 | ->hasMessage('Mapped class "invalid_FQDN" is not a valid FQDN'); 128 | } 129 | 130 | public function test__toString() 131 | { 132 | // Should return the attribute 133 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 134 | $this->assert 135 | ->string($definition->__toString()) 136 | ->isEqualTo('attribute'); 137 | } 138 | 139 | public function testGetAttribute() 140 | { 141 | // Should return the attribute 142 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 143 | $this->assert 144 | ->string($definition->getAttribute()) 145 | ->isEqualTo('attribute'); 146 | } 147 | 148 | public function testGetKey() 149 | { 150 | // Should return the key 151 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 152 | $this->assert 153 | ->string($definition->getKey()) 154 | ->isEqualTo('key'); 155 | } 156 | 157 | public function testGetMutator() 158 | { 159 | // Should return the mutator 160 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 161 | $this->assert 162 | ->string($definition->getMutator()) 163 | ->isEqualTo('mutator'); 164 | } 165 | 166 | public function testGetAccessor() 167 | { 168 | // Should return the accessor 169 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 170 | $this->assert 171 | ->string($definition->getAccessor()) 172 | ->isEqualTo('accessor'); 173 | } 174 | 175 | public function testIsComposite() 176 | { 177 | // Should return true for the default type "mixed" 178 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 179 | $this->assert 180 | ->boolean($definition->isComposite()) 181 | ->isTrue(); 182 | 183 | // Should return false for a supported type string 184 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'string')); 185 | $this->assert 186 | ->boolean($definition->isComposite()) 187 | ->isFalse(); 188 | 189 | // Should return false for a supported pseudo type number 190 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'number')); 191 | $this->assert 192 | ->boolean($definition->isComposite()) 193 | ->isFalse(); 194 | 195 | // Should return true for the supported type array 196 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array')); 197 | $this->assert 198 | ->boolean($definition->isComposite()) 199 | ->isTrue(); 200 | 201 | // Should return true for the supported pseudo type object 202 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'object')); 203 | $this->assert 204 | ->boolean($definition->isComposite()) 205 | ->isTrue(); 206 | } 207 | 208 | public function testIsMapped() 209 | { 210 | // Should return true if type is a FQDN 211 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 212 | $this->assert 213 | ->boolean($definition->isMapped()) 214 | ->isTrue(); 215 | 216 | // Should return true if type is array and mappedClass is FQDN 217 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 218 | $this->assert 219 | ->boolean($definition->isMapped()) 220 | ->isTrue(); 221 | 222 | // Should return false if type is supported and non mappable 223 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'string')); 224 | $this->assert 225 | ->boolean($definition->isMapped()) 226 | ->isFalse(); 227 | 228 | // Should return false if type is array and no mappedClass is provided 229 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array')); 230 | $this->assert 231 | ->boolean($definition->isMapped()) 232 | ->isFalse(); 233 | } 234 | 235 | public function testGetMappedType() 236 | { 237 | // Should return null 238 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 239 | $this->assert 240 | ->variable($definition->getMappedType()) 241 | ->isNull(); 242 | 243 | // Should return Boomgo\Builder\Definition::DOCUMENT (document) for a single embedded document 244 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 245 | $this->assert 246 | ->string($definition->getMappedType()) 247 | ->isEqualTo(Src\Definition::DOCUMENT); 248 | 249 | // Should return Boomgo\Builder\Definition::COLLECTION (collection) for an embedded collection 250 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 251 | $this->assert 252 | ->string($definition->getMappedType()) 253 | ->isEqualTo(Src\Definition::COLLECTION); 254 | } 255 | 256 | public function testGetMappedClass() 257 | { 258 | // Should return null 259 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 260 | $this->assert 261 | ->variable($definition->getMappedType()) 262 | ->isNull(); 263 | 264 | // Should return the FQDN of the single embedded document with the beginning \ 265 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 266 | $this->assert 267 | ->string($definition->getMappedClass()) 268 | ->isEqualTo('\\User\\Namespace\\Object'); 269 | 270 | // Should return the document FQDN of an embedded collection with the beginning \ 271 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 272 | $this->assert 273 | ->string($definition->getMappedClass()) 274 | ->isEqualTo('\\User\\Namespace\\Object'); 275 | } 276 | 277 | public function testGetMappedClassName() 278 | { 279 | // Should return the short class name without namespace part for a single embedded document 280 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 281 | $this->assert 282 | ->string($definition->getMappedClassName()) 283 | ->isEqualTo('Object'); 284 | 285 | // Should return the short class name without namespace part for an embedded collection 286 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 287 | $this->assert 288 | ->string($definition->getMappedClassName()) 289 | ->isEqualTo('Object'); 290 | } 291 | 292 | public function testGetMappedNamespace() 293 | { 294 | // Should return the namespace part without the class name for a single embedded document with the beginning \ 295 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 296 | $this->assert 297 | ->string($definition->getMappedNamespace()) 298 | ->isEqualTo('\\User\\Namespace'); 299 | 300 | // Should return the namespace part without the class name for an embedded collection with the beginning \ 301 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 302 | $this->assert 303 | ->string($definition->getMappedNamespace()) 304 | ->isEqualTo('\\User\\Namespace'); 305 | } 306 | 307 | public function testIsDocumentMapped() 308 | { 309 | // Should return false if type is a FQDN 310 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 311 | $this->assert 312 | ->boolean($definition->isDocumentMapped()) 313 | ->isTrue(); 314 | 315 | foreach (Src\Definition::$nativeClasses as $nativeClass => $boolean) { 316 | 317 | // Should return false if type is a native supported FQDN 318 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => $nativeClass)); 319 | $this->assert 320 | ->boolean($definition->isDocumentMapped()) 321 | ->isTrue(); 322 | } 323 | 324 | // Should return false if type is array and mappedClass is FQDN 325 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 326 | $this->assert 327 | ->boolean($definition->isDocumentMapped()) 328 | ->isFalse(); 329 | 330 | // Should return false if type is not a FQDN 331 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'string')); 332 | $this->assert 333 | ->boolean($definition->isDocumentMapped()) 334 | ->isFalse(); 335 | } 336 | 337 | public function testIsCollectionMapped() 338 | { 339 | // Should return true if type is array and mappedClass is FQDN 340 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 341 | $this->assert 342 | ->boolean($definition->isCollectionMapped()) 343 | ->isTrue(); 344 | 345 | foreach (Src\Definition::$nativeClasses as $nativeClass => $boolean) { 346 | 347 | // Should return true if type is array and mappedClass a native supported FQDN 348 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => $nativeClass)); 349 | $this->assert 350 | ->boolean($definition->isCollectionMapped()) 351 | ->isTrue(); 352 | } 353 | 354 | // Should return false if type is a FQDN 355 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 356 | $this->assert 357 | ->boolean($definition->isCollectionMapped()) 358 | ->isFalse(); 359 | 360 | // Should return false if type is not a FQDN 361 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array')); 362 | $this->assert 363 | ->boolean($definition->isCollectionMapped()) 364 | ->isFalse(); 365 | } 366 | 367 | public function testIsNativeMapped() 368 | { 369 | foreach (Src\Definition::$nativeClasses as $nativeClass => $boolean) { 370 | 371 | // Should return true for each native types embedded as single document 372 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => $nativeClass)); 373 | $this->assert 374 | ->boolean($definition->isNativeMapped()) 375 | ->isTrue(); 376 | 377 | // Should return true for each native types embedded as collection of documents 378 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => $nativeClass)); 379 | $this->assert 380 | ->boolean($definition->isNativeMapped()) 381 | ->isTrue(); 382 | } 383 | 384 | // Should return false if type isn't a FQDN 385 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator')); 386 | $this->assert 387 | ->boolean($definition->isNativeMapped()) 388 | ->isFalse(); 389 | 390 | // Should return false if type is a FQDN and isn't natively supported 391 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 392 | $this->assert 393 | ->boolean($definition->isNativeMapped()) 394 | ->isFalse(); 395 | 396 | // Should return false for non native FQDN type 397 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 398 | $this->assert 399 | ->boolean($definition->isNativeMapped()) 400 | ->isFalse(); 401 | } 402 | 403 | public function testIsUserMapped() 404 | { 405 | // Should return true if type is a custom user FQDN 406 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'User\\Namespace\\Object')); 407 | $this->assert 408 | ->boolean($definition->isUserMapped()) 409 | ->isTrue(); 410 | 411 | // Should return true if type is array and mappedClass is a custom FQDN 412 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => 'User\\Namespace\\Object')); 413 | $this->assert 414 | ->boolean($definition->isUserMapped()) 415 | ->isTrue(); 416 | 417 | foreach (Src\Definition::$nativeClasses as $nativeClass => $boolean) { 418 | 419 | // Should return false if type is a native supported FQDN 420 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => $nativeClass)); 421 | $this->assert 422 | ->boolean($definition->isUserMapped()) 423 | ->isFalse(); 424 | 425 | // Should return false if type is array and mappedClass a native supported FQDN 426 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'array', 'mappedClass' => $nativeClass)); 427 | $this->assert 428 | ->boolean($definition->isUserMapped()) 429 | ->isFalse(); 430 | } 431 | 432 | // Should return false if type isn't a FQDN 433 | $definition = new Src\Definition(array('attribute' => 'attribute', 'key' => 'key', 'accessor' => 'accessor', 'mutator' => 'mutator', 'type' => 'string')); 434 | $this->assert 435 | ->boolean($definition->isUserMapped()) 436 | ->isFalse(); 437 | } 438 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Builder/Generator/AbstractGenerator.php: -------------------------------------------------------------------------------- 1 | mockClass('Boomgo\\Parser\\ParserInterface', '\\Mock\\Parser', 'Parser'); 14 | $this->mockClass('Boomgo\\Formatter\\FormatterInterface', '\\Mock\\Formatter', 'Formatter'); 15 | $this->mockClass('Boomgo\\Builder\\MapBuilder', '\\Mock\\Builder', 'MapBuilder'); 16 | $this->mockClass('TwigGenerator\\Builder\\Generator', '\\Mock\\Builder', 'TwigGenerator'); 17 | $this->mockClass('Boomgo\\Builder\\Generator\\AbstractGenerator', '\\Mock\\Builder\\Generator', 'AbstractGenerator'); 18 | 19 | $mockMapBuilder = $this->getMockMapBuilder(); 20 | $mockTwigGenerator = $this->getMockTwigGenerator(); 21 | 22 | $this->generator = new \Mock\Builder\Generator\AbstractGenerator($mockMapBuilder, $mockTwigGenerator); 23 | } 24 | 25 | public function afterTestMethod($method) 26 | { 27 | $this->generator = null; 28 | } 29 | 30 | /** 31 | * getMockMapBuilder 32 | * 33 | * @return \Mock\Builder\MapBuilder 34 | */ 35 | public function getMockMapBuilder() 36 | { 37 | $mockParser = new \Mock\Parser\Parser(); 38 | $mockFormatter = new \Mock\Formatter\Formatter(); 39 | $mockMapBuilder = new \Mock\Builder\MapBuilder($mockParser, $mockFormatter); 40 | 41 | return $mockMapBuilder; 42 | } 43 | 44 | /** 45 | * getMockTwigGenerator 46 | * 47 | * @return \Mock\Builder\TwigGenerator 48 | */ 49 | public function getMockTwigGenerator() 50 | { 51 | // Avoid constructor call for TwigGenerator with creating directory 52 | $controller = new \mageekguy\atoum\mock\controller(); 53 | $controller->__construct = function() {}; 54 | 55 | $mockTwigGenerator = new \Mock\Builder\TwigGenerator($controller); 56 | 57 | return $mockTwigGenerator; 58 | } 59 | 60 | public function testConstruct() 61 | { 62 | $mockMapBuilder = $this->getMockMapBuilder(); 63 | $mockTwigGenerator = $this->getMockTwigGenerator(); 64 | 65 | $generator = new \Mock\Builder\Generator\AbstractGenerator($mockMapBuilder, $mockTwigGenerator); 66 | 67 | $this->assert() 68 | ->object($this->generator) 69 | ->isInstanceOf('Boomgo\\Builder\\Generator\\AbstractGenerator'); 70 | } 71 | 72 | public function testSetterGetter() 73 | { 74 | $anotherMockMapBuilder = $this->getMockMapBuilder(); 75 | $this->generator->setMapBuilder($anotherMockMapBuilder); 76 | 77 | $this->assert() 78 | ->variable($this->generator->getMapBuilder()) 79 | ->isIdenticalTo($anotherMockMapBuilder); 80 | 81 | $anotherMockTwigGenerator = $this->getMockTwigGenerator(); 82 | $this->generator->setTwigGenerator($anotherMockTwigGenerator); 83 | 84 | $this->assert() 85 | ->variable($this->generator->getTwigGenerator()) 86 | ->isIdenticalTo($anotherMockTwigGenerator); 87 | } 88 | 89 | public function testLoad() 90 | { 91 | // Closure doesn't accept $this context 92 | $generator = $this->generator; 93 | 94 | // Should throw an exception if argument is a string and not a valid file or directory 95 | $this->assert 96 | ->exception(function() use ($generator) { 97 | $generator->load('invalid path'); 98 | }) 99 | ->isInstanceOf('InvalidArgumentException') 100 | ->hasMessage('Argument must be an absolute directory or a file path or both in an array'); 101 | 102 | // Should throw an exception if argument is an array and an element is not a valid file or directory 103 | $this->assert 104 | ->exception(function() use ($generator) { 105 | $generator->load(array(__FILE__, 'invalid path')); 106 | }) 107 | ->isInstanceOf('InvalidArgumentException') 108 | ->hasMessage('Argument must be an absolute directory or a file path or both in an array'); 109 | 110 | 111 | // Should return an array containing the realpath of a filename when providing a filename 112 | $this->assert 113 | ->array($generator->load(__DIR__.'/../../Fixture/Annoted/Document.php')) 114 | ->hasSize(1) 115 | ->strictlyContainsValues(array(realpath(__DIR__.'/../../Fixture/Annoted/Document.php'))); 116 | 117 | // Should return an array containing many files realpath when providing a directory 118 | $this->assert 119 | ->array($generator->load(__DIR__.'/../../Fixture/Annoted')) 120 | ->hasSize(2) 121 | ->strictlyContainsValues(array(realpath(__DIR__.'/../../Fixture/Annoted/Document.php'), realpath(__DIR__.'/../../Fixture/Annoted/DocumentEmbed.php'))); 122 | 123 | // Should return an array containing many files realpath when providing an array of directory 124 | $this->assert 125 | ->array($generator->load(array(__DIR__.'/../../Fixture/Annoted', __DIR__.'/../../Fixture/AnotherAnnoted'))) 126 | ->hasSize(3) 127 | ->strictlyContainsValues(array( 128 | realpath(__DIR__.'/../../Fixture/Annoted/Document.php'), 129 | realpath(__DIR__.'/../../Fixture/Annoted/DocumentEmbed.php'), 130 | realpath(__DIR__.'/../../Fixture/AnotherAnnoted/Document.php'))); 131 | 132 | // Should return an array containing many files realpath when providing an array mixed with directory and file 133 | $this->assert 134 | ->array($generator->load(array(__DIR__.'/../../Fixture/Annoted', __DIR__.'/../../Fixture/AnotherAnnoted/Document.php'))) 135 | ->hasSize(3) 136 | ->strictlyContainsValues(array( 137 | realpath(__DIR__.'/../../Fixture/Annoted/Document.php'), 138 | realpath(__DIR__.'/../../Fixture/Annoted/DocumentEmbed.php'), 139 | realpath(__DIR__.'/../../Fixture/AnotherAnnoted/Document.php'))); 140 | } 141 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Builder/Generator/MapperGenerator.php: -------------------------------------------------------------------------------- 1 | mockClass('Boomgo\\Parser\\ParserInterface', '\\Mock\\Parser', 'Parser'); 16 | $this->mockClass('Boomgo\\Formatter\\FormatterInterface', '\\Mock\\Formatter', 'Formatter'); 17 | $this->mockClass('Boomgo\\Builder\\Map', '\\Mock\\Builder', 'Map'); 18 | $this->mockClass('Boomgo\\Builder\\MapBuilder', '\\Mock\\Builder', 'MapBuilder'); 19 | $this->mockClass('TwigGenerator\\Builder\\Generator', '\\Mock\\Builder', 'TwigGenerator'); 20 | 21 | $mockParser = new \Mock\Parser\Parser(); 22 | $mockFormatter = new \Mock\Formatter\Formatter(); 23 | 24 | $mockMap = new \Mock\Builder\Map('Boomgo\\Tests\\Units\\Fixture\\AnotherAnnoted\\Document'); 25 | $mockMap->getMockController()->getClassName = function() { return 'Document'; }; 26 | $mockMap->getMockController()->getNamespace = function() { return 'Boomgo\\Tests\\Units\\Fixture\\AnotherAnnoted'; }; 27 | 28 | $mockMapBuilder = new \Mock\Builder\MapBuilder($mockParser, $mockFormatter); 29 | $mockMapBuilder->getMockController()->build = function() use ($mockMap) { return array($mockMap); }; 30 | 31 | // Avoid constructor call for TwigGenerator with creating directory 32 | $controllerTwigGenerator = new \mageekguy\atoum\mock\controller(); 33 | $controllerTwigGenerator->__construct = function() {}; 34 | $controllerTwigGenerator->writeOndisk = function() {}; 35 | $mockTwigGenerator = new \Mock\Builder\TwigGenerator($controllerTwigGenerator); 36 | 37 | $this->generator = new BaseMapperGenerator($mockMapBuilder, $mockTwigGenerator); 38 | } 39 | 40 | public function afterTestMethod($method) 41 | { 42 | $this->generator = null; 43 | } 44 | 45 | public function testGenerate() 46 | { 47 | $generator = $this->generator; 48 | 49 | $this->assert 50 | ->exception(function () use ($generator) { 51 | $generator->generate(array(__DIR__.'/../../Fixture/AnotherAnnoted.php'), 'Document', 'Mapper', '/Fixture'); 52 | }) 53 | ->isInstanceOf('InvalidArgumentException') 54 | ->hasMessage('Boomgo support only PSR-O structure, your namespace "Document" doesn\'t reflect your directory structure "/Fixture"'); 55 | 56 | $this->assert 57 | ->exception(function () use ($generator) { 58 | $generator->generate(array(__DIR__.'/../../Fixture/AnotherAnnoted'), 'Document', 'Mapper', __DIR__.'/../../Document/'); 59 | }) 60 | ->isInstanceOf('RuntimeException') 61 | ->hasMessage('The Document map "\Boomgo\Tests\Units\Fixture\AnotherAnnoted\Document" doesn\'t include the document base namespace "Document"'); 62 | 63 | $this->assert 64 | ->boolean($generator->generate(array(__DIR__.'/../../Fixture/AnotherAnnoted/Document.php'), 'Fixture', 'Mapper', __DIR__.'/../Fixture/')) 65 | ->isTrue(); 66 | } 67 | 68 | /** 69 | * @todo test generated code 70 | */ 71 | // public function testFunctionalGenerate() 72 | // { 73 | // $formatter = new \Boomgo\Formatter\CamelCaseFormatter(); 74 | // $parser = new \Boomgo\Parser\AnnotationParser(); 75 | // $mapBuilder = new \Boomgo\Builder\MapBuilder($parser, $formatter); 76 | // $twigGenerator = new \TwigGenerator\Builder\Generator(); 77 | // $generator = new Builder\Generator\MapperGenerator($mapBuilder, $twigGenerator, array('namespace' => array('models' => 'Fixture', 'mappers' => 'Mapper'))); 78 | // $generator->generate(array(__DIR__.'/../Fixture/Annoted/Document.php', __DIR__.'/../Fixture/Annoted/DocumentEmbed.php')); 79 | // } 80 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Builder/Map.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Builder; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Builder as Src; 19 | 20 | /** 21 | * Map tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class Map extends Test 26 | { 27 | public function test__construct() 28 | { 29 | // Should throw an error if argument string (FQDN) is not provided 30 | $this->assert 31 | ->error(function() { 32 | new Src\Map(); 33 | }) 34 | ->withType(E_RECOVERABLE_ERROR); 35 | 36 | // Should initialize object and define class & empty arrays 37 | $map = new Src\Map('FQDN'); 38 | $this->assert 39 | ->string($map->getClass()) 40 | ->isEqualTo('\FQDN') 41 | ->array($map->getMongoIndex()) 42 | ->isEmpty() 43 | ->array($map->getDefinitions()) 44 | ->isEmpty(); 45 | } 46 | 47 | public function testGetClass() 48 | { 49 | // Should return the mapped FQDN with the first \ 50 | $map = new Src\Map('FQDN'); 51 | $this->assert 52 | ->string($map->getClass()) 53 | ->isEqualTo('\FQDN'); 54 | } 55 | 56 | public function testGetClassname() 57 | { 58 | // Should return the short class name without namespace part 59 | $map = new Src\Map('Vendor\\Package\\Subpackage\\Class'); 60 | $this->assert 61 | ->string($map->getClassName()) 62 | ->isEqualTo('Class'); 63 | } 64 | 65 | public function testGetNamespace() 66 | { 67 | // Should return the namespace without the short class name part and with the first \ 68 | $map = new Src\Map('Vendor\\Package\\Subpackage\\Class'); 69 | $this->assert 70 | ->string($map->getNamespace()) 71 | ->isEqualTo('\\Vendor\\Package\\Subpackage'); 72 | } 73 | 74 | 75 | public function testAddDefinition() 76 | { 77 | // Should append an item to mongoIndex and Definition 78 | $map = new Src\Map('FQDN'); 79 | $mockDefinition = $this->mockDefinitionProvider(); 80 | $map->addDefinition($mockDefinition); 81 | $this->assert 82 | ->array($map->getMongoIndex()) 83 | ->hasSize(1) 84 | ->isIdenticalTo(array('key' => 'attribute')) 85 | ->array($map->getDefinitions()) 86 | ->hasSize(1) 87 | ->isIdenticalTo(array('attribute' => $mockDefinition)); 88 | } 89 | 90 | public function testHasDefinition() 91 | { 92 | // Should return false when looking for an unknown attribute 93 | $map = new Src\Map('FQDN'); 94 | $this->assert 95 | ->boolean($map->hasDefinition('unkown')) 96 | ->isFalse(); 97 | 98 | // Should return true for an existing "PHP attribute" or "MongoDB key" 99 | $mockDefinition = $this->mockDefinitionProvider(); 100 | $map->addDefinition($mockDefinition); 101 | $this->assert 102 | ->boolean($map->hasDefinition('attribute')) 103 | ->isTrue() 104 | ->boolean($map->hasDefinition('key')) 105 | ->istrue(); 106 | } 107 | 108 | public function testGetDefinition() 109 | { 110 | // Should return null when getting with an unknown attribute 111 | $map = new Src\Map('FQDN'); 112 | $this->assert 113 | ->variable($map->getDefinition('unkown')) 114 | ->isNull(); 115 | 116 | // Should return true when getting with an existing "PHP attribute" or "MongoDB key" 117 | $mockDefinition = $this->mockDefinitionProvider(); 118 | $map->addDefinition($mockDefinition); 119 | $this->assert 120 | ->object($map->getDefinition('attribute')) 121 | ->isIdenticalTo($mockDefinition) 122 | ->object($map->getDefinition('key')) 123 | ->isIdenticalTo($mockDefinition); 124 | } 125 | 126 | private function mockDefinitionProvider() 127 | { 128 | $this->mockClass('Boomgo\\Builder\\Definition', '\\Mock\\Map', 'Definition'); 129 | 130 | $mockController = new \mageekguy\atoum\mock\controller(); 131 | $mockController->__construct = function() {}; 132 | $mockController->controlNextNewMock(); 133 | 134 | $mockDefinition = new \Mock\Map\Definition(array()); 135 | $mockDefinition->getMockController()->getAttribute = 'attribute'; 136 | $mockDefinition->getMockController()->getKey = 'key'; 137 | 138 | return $mockDefinition; 139 | } 140 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Builder/MapBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Builder; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Builder; 19 | 20 | /** 21 | * Builder tests 22 | * 23 | * @author Ludovic Fleury 24 | * @author David Guyon 25 | */ 26 | class MapBuilder extends Test 27 | { 28 | public function test__construct() 29 | { 30 | // Should define the parser and the formatter 31 | $builder = $this->builderProvider(); 32 | $this->assert 33 | ->object($builder->getParser()) 34 | ->isInstanceOf('\\Boomgo\\Parser\\ParserInterface') 35 | ->object($builder->getFormatter()) 36 | ->isInstanceOf('\\Boomgo\\Formatter\\FormatterInterface') 37 | ->string($builder->getMapClassName()) 38 | ->isEqualTo('Boomgo\\Builder\\Map') 39 | ->string($builder->getDefinitionClassName()) 40 | ->isEqualTo('Boomgo\\Builder\\Definition'); 41 | } 42 | 43 | public function testGetParser() 44 | { 45 | // Should return the parser 46 | $builder = $this->builderProvider(); 47 | $this->assert 48 | ->object($builder->getParser()) 49 | ->isInstanceOf('\\Boomgo\\Parser\\ParserInterface'); 50 | } 51 | 52 | public function testGetFormatter() 53 | { 54 | // Should return the formatter 55 | $builder = $this->builderProvider(); 56 | $this->assert 57 | ->object($builder->getFormatter()) 58 | ->isInstanceOf('\\Boomgo\\Formatter\\FormatterInterface'); 59 | } 60 | 61 | public function testMutatorsAndAccessors() 62 | { 63 | $mapbuilder = $this->builderProvider(); 64 | 65 | $mapbuilder->setMapClassName('My\\New\\Map'); 66 | $this->assert() 67 | ->string($mapbuilder->getMapClassName()) 68 | ->isEqualTo('My\\New\\Map'); 69 | 70 | $mapbuilder->setDefinitionClassName('My\\New\\Definition'); 71 | $this->assert() 72 | ->string($mapbuilder->getDefinitionClassName()) 73 | ->isEqualTo('My\\New\\Definition'); 74 | } 75 | 76 | public function testBuild() 77 | { 78 | $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixture'; 79 | $annotedDir = $fixtureDir.DIRECTORY_SEPARATOR.'Annoted'; 80 | 81 | // Should build Maps for an array of files 82 | $builder = $this->builderProvider(); 83 | $processed = $builder->build(array($annotedDir.DIRECTORY_SEPARATOR.'Document.php', $annotedDir.DIRECTORY_SEPARATOR.'DocumentEmbed.php')); 84 | $this->assert 85 | ->array($processed) 86 | ->hasSize(2) 87 | ->object($processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document']) 88 | ->isInstanceOf('\\Boomgo\\Builder\\Map') 89 | ->object($processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\DocumentEmbed']) 90 | ->isInstanceOf('\\Boomgo\\Builder\\Map') 91 | ->array($processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document']->getDefinitions()) 92 | ->hasSize(5) 93 | ->hasKeys(array('id', 'string', 'array', 'document', 'collection')) 94 | ->array($processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\DocumentEmbed']->getDefinitions()) 95 | ->hasSize(2) 96 | ->hasKeys(array('string', 'array')); 97 | 98 | // Should throw exception on invalid metadata 99 | $invalidMetadata = array( 100 | 'class' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document', 101 | 'definitions' => array( 102 | array(), 103 | array('attribute' => 'string', 'type' => 'string'))); 104 | 105 | $mockParser = new \mock\Boomgo\Parser\ParserInterface; 106 | $mockParser->getMockController()->supports = function() { return true; }; 107 | $mockParser->getMockController()->getExtension = function() { return 'php'; }; 108 | $mockParser->getMockController()->parse = function($file) use ($invalidMetadata) { return $invalidMetadata; }; 109 | 110 | $mockFormatter = new \mock\Boomgo\Formatter\FormatterInterface; 111 | 112 | $builder = new Builder\MapBuilder($mockParser, $mockFormatter); 113 | $this->assert 114 | ->exception(function() use ($builder) { 115 | $builder->build(array(__FILE__)); // random we do not care since parser is a mock 116 | }) 117 | ->isInstanceOf('RuntimeException') 118 | ->hasMessage('Invalid metadata should provide an attribute or a key'); 119 | 120 | // Should guess the attribute if only a key is provided 121 | $keyMetadata = array( 122 | 'class' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document', 123 | 'definitions' => array( 124 | array('key' => 'STRING', 'type' => 'string'))); 125 | 126 | $mockParser = new \mock\Boomgo\Parser\ParserInterface; 127 | $mockParser->getMockController()->supports = function() { return true; }; 128 | $mockParser->getMockController()->getExtension = function() { return 'php'; }; 129 | $mockParser->getMockController()->parse = function($file) use ($keyMetadata) { return $keyMetadata; }; 130 | 131 | $mockFormatter = new \mock\Boomgo\Formatter\FormatterInterface; 132 | $mockFormatter->getMockController()->toPhpAttribute = function($string) { return strtolower($string); }; 133 | $mockFormatter->getMockController()->toMongoKey = function($string) { return strtoupper($string); }; 134 | $mockFormatter->getMockController()->getPhpAccessor = function($string) { return 'get'.ucfirst($string); }; 135 | $mockFormatter->getMockController()->getPhpMutator = function($string) { return 'set'.ucfirst($string); }; 136 | 137 | $builder = new Builder\MapBuilder($mockParser, $mockFormatter); 138 | $processed = $builder->build(array(__FILE__)); // random we do not care since parser is a mock 139 | $this->assert 140 | ->array($processed) 141 | ->hasSize(1) 142 | ->object($processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document']) 143 | ->isInstanceOf('\\Boomgo\\Builder\\Map'); 144 | 145 | $definitions = $processed['\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document']->getDefinitions(); 146 | $this->assert 147 | ->object($definitions['string']) 148 | ->string($definitions['string']->getAttribute()) 149 | ->isEqualTo('string'); 150 | 151 | } 152 | 153 | private function builderprovider() 154 | { 155 | $fixtureMetadata = $this->metadataProvider(); 156 | 157 | $mockParser = new \mock\Boomgo\Parser\ParserInterface; 158 | $mockParser->getMockController()->supports = function() { return true; }; 159 | $mockParser->getMockController()->getExtension = function() { return 'php'; }; 160 | $mockParser->getMockController()->parse = function($file) use ($fixtureMetadata) { return $fixtureMetadata[$file]; }; 161 | 162 | $mockFormatter = new \mock\Boomgo\Formatter\FormatterInterface; 163 | $mockFormatter->getMockController()->toPhpAttribute = function($string) { return strtolower($string); }; 164 | $mockFormatter->getMockController()->toMongoKey = function($string) { return strtoupper($string); }; 165 | $mockFormatter->getMockController()->getPhpAccessor = function($string) { return 'get'.ucfirst($string); }; 166 | $mockFormatter->getMockController()->getPhpMutator = function($string) { return 'set'.ucfirst($string); }; 167 | 168 | return new Builder\MapBuilder($mockParser, $mockFormatter); 169 | } 170 | 171 | private function metadataProvider() 172 | { 173 | $document = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixture'.DIRECTORY_SEPARATOR.'Annoted'.DIRECTORY_SEPARATOR.'Document.php'; 174 | $embed = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixture'.DIRECTORY_SEPARATOR.'Annoted'.DIRECTORY_SEPARATOR.'DocumentEmbed.php'; 175 | $another = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixture'.DIRECTORY_SEPARATOR.'AnotherAnnoted'.DIRECTORY_SEPARATOR.'Document.php'; 176 | 177 | return array( 178 | $document => array( 179 | 'class' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\Document', 180 | 'definitions' => array( 181 | array('attribute' => 'id', 'type' => 'string'), 182 | array('attribute' => 'string', 'type' => 'string'), 183 | array('attribute' => 'array', 'type' => 'array'), 184 | array('attribute' => 'document', 'type' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\DocumentEmbed'), 185 | array('attribute' => 'collection', 'type' => 'array', 'mappedClass' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\DocumentEmbed' ))), 186 | $embed => array( 187 | 'class' => '\\Boomgo\\Tests\\Units\\Fixture\\Annoted\\DocumentEmbed', 188 | 'definitions' => array( 189 | array('attribute' => 'string', 'type' => 'string'), 190 | array('attribute' => 'array', 'type' => 'array'))), 191 | $another => array( 192 | 'class' => '\\Boomgo\\Tests\\Units\\Fixture\\AnotherAnnoted\\Document', 193 | 'definitions' => array( 194 | array('attribute' => 'id', 'type' => 'string'), 195 | array('attribute' => 'string', 'type' => 'string'), 196 | array('attribute' => 'array', 'type' => 'array'), 197 | array('attribute' => 'document', 'type' => '\\Boomgo\\Tests\\Units\\Fixture\\AnotherAnnoted\\Document'))) 198 | ); 199 | } 200 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Cache/ArrayCache.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Cache; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Cache; 19 | 20 | /** 21 | * ArrayCache tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class ArrayCache extends Test 26 | { 27 | public function testHas() 28 | { 29 | $cache = new Cache\ArrayCache(); 30 | 31 | // Should return false when the resource don't exist in cache 32 | $this->assert 33 | ->boolean($cache->has('identifier')) 34 | ->isFalse(); 35 | 36 | // Should return true when the resource is cached 37 | $cache->add('identifier', 'dummy'); 38 | $this->assert 39 | ->boolean($cache->has('identifier')) 40 | ->isTrue(); 41 | } 42 | 43 | public function testGet() 44 | { 45 | $cache = new Cache\ArrayCache(); 46 | 47 | // Should return null when the resource don't exist in cache 48 | $this->assert 49 | ->variable($cache->get('identifier')) 50 | ->isNull(); 51 | 52 | // Should return the cached resource 53 | $cache->add('identifier', 'dummy'); 54 | $this->assert 55 | ->string($cache->get('identifier')) 56 | ->isEqualTo('dummy'); 57 | } 58 | 59 | public function testAdd() 60 | { 61 | $cache = new Cache\ArrayCache(); 62 | 63 | // Should cache a resource 64 | $cache->add('identifier', 'dummy'); 65 | $this->assert 66 | ->boolean($cache->has('identifier')) 67 | ->isTrue() 68 | ->string($cache->get('identifier')) 69 | ->isEqualTo('dummy'); 70 | 71 | } 72 | 73 | public function testRemove() 74 | { 75 | $cache = new Cache\ArrayCache(); 76 | 77 | // Should remove a specific cached resource 78 | $cache->add('identifier', 'dummy'); 79 | $cache->add('another', 'resource'); 80 | $cache->remove('identifier'); 81 | $this->assert 82 | ->boolean($cache->has('identifier')) 83 | ->isFalse() 84 | ->boolean($cache->has('another')) 85 | ->isTrue(); 86 | } 87 | 88 | public function testClear() 89 | { 90 | $cache = new Cache\ArrayCache(); 91 | 92 | // Should remove a cached resource 93 | $cache->add('identifier', 'dummy'); 94 | $cache->add('another', 'resource'); 95 | $cache->clear(); 96 | $this->assert 97 | ->boolean($cache->has('identifier')) 98 | ->isFalse() 99 | ->boolean($cache->has('another')) 100 | ->isFalse(); 101 | } 102 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Cache/NoCache.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Cache; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Cache; 19 | 20 | /** 21 | * NoCache tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class NoCache extends Test 26 | { 27 | public function testHas() 28 | { 29 | $cache = new Cache\NoCache(); 30 | 31 | // Should return false 32 | $this->assert 33 | ->boolean($cache->has('identifier')) 34 | ->isFalse(); 35 | } 36 | 37 | public function testGet() 38 | { 39 | $cache = new Cache\NoCache(); 40 | 41 | // Should return null 42 | $this->assert 43 | ->variable($cache->get('identifier')) 44 | ->isNull(); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Fixture/Annotation.php: -------------------------------------------------------------------------------- 1 | id; 58 | } 59 | 60 | public function setId($id) 61 | { 62 | $this->id =$id; 63 | } 64 | 65 | public function setString($value) 66 | { 67 | $this->string = $value; 68 | } 69 | 70 | public function getString() 71 | { 72 | return $this->string; 73 | } 74 | 75 | public function setNumber($value) 76 | { 77 | $this->umber = $value; 78 | } 79 | 80 | public function getNumber() 81 | { 82 | return $this->umber; 83 | } 84 | 85 | public function setAttribute($value) 86 | { 87 | $this->attribute = $value; 88 | } 89 | 90 | public function getAttribute() 91 | { 92 | return $this->attribute; 93 | } 94 | 95 | public function setArray($value) 96 | { 97 | $this->array = $value; 98 | } 99 | 100 | public function getArray() 101 | { 102 | return $this->array; 103 | } 104 | 105 | public function setDocument($value) 106 | { 107 | $this->document = $value; 108 | } 109 | 110 | public function getDocument() 111 | { 112 | return $this->document; 113 | } 114 | 115 | public function setCollection($value) 116 | { 117 | $this->collection = $value; 118 | } 119 | 120 | public function getCollection() 121 | { 122 | return $this->collection; 123 | } 124 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Fixture/Annoted/DocumentEmbed.php: -------------------------------------------------------------------------------- 1 | string = $value; 23 | } 24 | 25 | public function getString() 26 | { 27 | return $this->string; 28 | } 29 | 30 | public function setArray($value) 31 | { 32 | $this->array = $value; 33 | } 34 | 35 | public function getArray() 36 | { 37 | return $this->array; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Fixture/AnotherAnnoted/Document.php: -------------------------------------------------------------------------------- 1 | id; 41 | } 42 | 43 | public function setId($id) 44 | { 45 | $this->id =$id; 46 | } 47 | 48 | public function setString($value) 49 | { 50 | $this->string = $value; 51 | } 52 | 53 | public function getString() 54 | { 55 | return $this->string; 56 | } 57 | 58 | public function setArray($value) 59 | { 60 | $this->array = $value; 61 | } 62 | 63 | public function getArray() 64 | { 65 | return $this->array; 66 | } 67 | 68 | public function setDocument($value) 69 | { 70 | $this->document = $value; 71 | } 72 | 73 | public function getDocument() 74 | { 75 | return $this->document; 76 | } 77 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Fixture/NoNamespace.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Formatter; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Formatter; 19 | 20 | /** 21 | * CamelCaseFormatter tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class CamelCaseFormatter extends Test 26 | { 27 | public function test__construct() 28 | { 29 | // Should implement FormatterInterface 30 | $this->assert 31 | ->class('Boomgo\Formatter\TransparentFormatter') 32 | ->hasInterface('Boomgo\Formatter\FormatterInterface'); 33 | } 34 | 35 | public function testToMongoKey() 36 | { 37 | $formatter = new Formatter\CamelCaseFormatter(); 38 | 39 | // Should return the exact provided string 40 | $this->assert 41 | ->string($formatter->toMongoKey('phpAttribute')) 42 | ->isEqualTo('phpAttribute'); 43 | 44 | // Should handle _id MongoDB exception and add the underscore 45 | $this->assert 46 | ->string($formatter->toMongoKey('id')) 47 | ->isEqualTo('_id'); 48 | } 49 | 50 | public function testToPhpAttribute() 51 | { 52 | $formatter = new Formatter\CamelCaseFormatter(); 53 | 54 | // Should return the exact provided string 55 | $this->assert 56 | ->string($formatter->toPhpAttribute('mongoKey')) 57 | ->isEqualTo('mongoKey'); 58 | 59 | // Should handle _id MongoDB exception and remove the underscore 60 | $this->assert 61 | ->string($formatter->toPhpAttribute('_id')) 62 | ->isEqualTo('id'); 63 | } 64 | 65 | public function testGetPhpAccessor() 66 | { 67 | $formatter = new Formatter\CamelCaseFormatter(); 68 | 69 | // Should prefixes with get and capitalizes first attribute letter 70 | $this->assert 71 | ->string($formatter->getPhpAccessor('mongoKey', 'mixed')) 72 | ->isEqualTo('getMongoKey'); 73 | 74 | // Should prefixes with is and capitalizes first attribute letter for a boolean type 75 | $this->assert 76 | ->string($formatter->getPhpAccessor('mongoKey', 'boolean')) 77 | ->isEqualTo('isMongoKey') 78 | ->string($formatter->getPhpAccessor('mongoKey', 'bool')) 79 | ->isEqualTo('isMongoKey'); 80 | } 81 | 82 | public function testGetPhpMutator() 83 | { 84 | $formatter = new Formatter\CamelCaseFormatter(); 85 | 86 | // Should prefixes with set and capitalize first attribute letter 87 | $this->assert 88 | ->string($formatter->getPhpMutator('mongoKey')) 89 | ->isEqualTo('setMongoKey'); 90 | } 91 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Formatter/TransparentFormatter.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Formatter; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Formatter; 19 | 20 | /** 21 | * TransparentFormatter tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class TransparentFormatter extends Test 26 | { 27 | public function test__construct() 28 | { 29 | // Should implement FormatterInterface 30 | $this->assert 31 | ->class('Boomgo\Formatter\TransparentFormatter') 32 | ->hasInterface('Boomgo\Formatter\FormatterInterface'); 33 | } 34 | 35 | public function testToMongoKey() 36 | { 37 | $formatter = new Formatter\TransparentFormatter(); 38 | 39 | // Should return the exact provided string 40 | $this->assert 41 | ->string($formatter->toMongoKey('FreeStyle php_AttrIbute')) 42 | ->isEqualTo('FreeStyle php_AttrIbute'); 43 | } 44 | 45 | public function testToPhpAttribute() 46 | { 47 | $formatter = new Formatter\TransparentFormatter(); 48 | 49 | // Should return the exact provided string 50 | $this->assert 51 | ->string($formatter->toPhpAttribute('FreeStyle MongoKey__')) 52 | ->isEqualTo('FreeStyle MongoKey__'); 53 | 54 | } 55 | 56 | public function testGetPhpAccessor() 57 | { 58 | $formatter = new Formatter\TransparentFormatter(); 59 | 60 | // Should always prefix the provided string with get or is 61 | $this->assert 62 | ->string($formatter->getPhpAccessor('FreeStyle MongoKey__')) 63 | ->isEqualTo('getFreeStyle MongoKey__') 64 | ->string($formatter->getPhpAccessor('FreeStyle MongoKey__', 'mixed')) 65 | ->isEqualTo('getFreeStyle MongoKey__') 66 | ->string($formatter->getPhpAccessor('FreeStyle MongoKey__', 'bool')) 67 | ->isEqualTo('isFreeStyle MongoKey__') 68 | ->string($formatter->getPhpAccessor('FreeStyle MongoKey__', 'boolean')) 69 | ->isEqualTo('isFreeStyle MongoKey__'); 70 | 71 | } 72 | 73 | public function testGetPhpMutator() 74 | { 75 | $formatter = new Formatter\TransparentFormatter(); 76 | 77 | // Should always prefix the provided string with set 78 | $this->assert 79 | ->string($formatter->getPhpMutator('FreeStyle MongoKey__')) 80 | ->isEqualTo('setFreeStyle MongoKey__') 81 | ->string($formatter->getPhpMutator('FreeStyle MongoKey__', 'mixed')) 82 | ->isEqualTo('setFreeStyle MongoKey__'); 83 | } 84 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Formatter/Underscore2CamelFormatter.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Formatter; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Formatter; 19 | 20 | /** 21 | * Underscore2CamelFormatter tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class Underscore2CamelFormatter extends Test 26 | { 27 | public function test__construct() 28 | { 29 | // Should implement FormatterInterface 30 | $this->assert 31 | ->class('Boomgo\Formatter\Underscore2CamelFormatter') 32 | ->hasInterface('Boomgo\Formatter\FormatterInterface'); 33 | } 34 | 35 | public function testToPhpAttribute() 36 | { 37 | $formatter = new Formatter\Underscore2CamelFormatter(); 38 | 39 | // Should (lower) camelize an underscored string 40 | $camelCase = $formatter->toPhpAttribute('hello_world_pol'); 41 | $this->assert 42 | ->string($camelCase) 43 | ->isEqualTo('helloWorldPol'); 44 | 45 | // Should handle prefixed or suffixed string with underscore 46 | $camelCase = $formatter->toPhpAttribute('_world_'); 47 | $this->assert 48 | ->string($camelCase) 49 | ->isEqualTo('world'); 50 | 51 | // Should handle double underscored string 52 | $camelCase = $formatter->toPhpAttribute('hello__world_'); 53 | $this->assert 54 | ->string($camelCase) 55 | ->isEqualTo('helloWorld'); 56 | 57 | // Should handle _id MongoDB exception and add the underscore 58 | $this->assert 59 | ->string($formatter->toPhpAttribute('_id')) 60 | ->isEqualTo('id'); 61 | } 62 | 63 | public function testToMongoAttribute() 64 | { 65 | $formatter = new Formatter\Underscore2CamelFormatter(); 66 | 67 | // Should underscore an upper CamelCase string 68 | $underscore = $formatter->toMongoKey('HelloWorldPol'); 69 | $this->assert 70 | ->string($underscore) 71 | ->isEqualTo('hello_world_pol'); 72 | 73 | // Should underscore a lower CamelCase string 74 | $underscore = $formatter->toMongoKey('helloWorld'); 75 | $this->assert 76 | ->string($underscore) 77 | ->isEqualTo('hello_world'); 78 | 79 | 80 | // Should handle mongoDB identifier 81 | $underscore = $formatter->toMongoKey('id'); 82 | $this->assert 83 | ->string($underscore) 84 | ->isEqualTo('_id'); 85 | } 86 | 87 | public function testGetPhpAccessor() 88 | { 89 | $formatter = new Formatter\Underscore2CamelFormatter(); 90 | 91 | // Should return a camelCase php accessor when explicitly providing a lower camelCase php attribute 92 | $this->assert 93 | ->string($formatter->getPhpAccessor('underscoredMongoKey', 'mixed')) 94 | ->isEqualTo('getUnderscoredMongoKey'); 95 | 96 | // Should return a camelCase php accessor when explicitly providing an upper camelCase php attribute 97 | $this->assert 98 | ->string($formatter->getPhpAccessor('UnderscoredMongoKey', 'mixed')) 99 | ->isEqualTo('getUnderscoredMongoKey'); 100 | } 101 | 102 | public function testGetPhpMutator() 103 | { 104 | $formatter = new Formatter\Underscore2CamelFormatter(); 105 | 106 | // Should return a camelCase php mutator when explicitly providing a lower camelCase php attribute 107 | $this->assert 108 | ->string($formatter->getPhpMutator('underscoredMongoKey')) 109 | ->isEqualTo('setUnderscoredMongoKey'); 110 | 111 | // Should return a camelCase php mutator when explicitly providing an upper camelCase php attribute 112 | $this->assert 113 | ->string($formatter->getPhpMutator('UnderscoredMongoKey')) 114 | ->isEqualTo('setUnderscoredMongoKey'); 115 | } 116 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Parser/AnnotationParser.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units\Parser; 16 | 17 | use Boomgo\Tests\Units\Test; 18 | use Boomgo\Parser; 19 | 20 | /** 21 | * AnnotationParser tests 22 | * 23 | * @author Ludovic Fleury 24 | */ 25 | class AnnotationParser extends Test 26 | { 27 | public function test__construct() 28 | { 29 | // Should be able to define the local and global annotation though the constructor 30 | $parser = new Parser\AnnotationParser('@Boomgo', '@MyHypeAnnot'); 31 | 32 | $this->assert 33 | ->string($parser->getLocalAnnotation()) 34 | ->isIdenticalTo('@MyHypeAnnot') 35 | ->string($parser->getGlobalAnnotation()) 36 | ->isIdenticalTo('@Boomgo'); 37 | } 38 | 39 | public function testSetGetGlobalAnnotation() 40 | { 41 | $parser = new Parser\AnnotationParser(); 42 | 43 | // Should set and get global annotation 44 | $parser->setGlobalAnnotation('@MyHypeAnnot'); 45 | 46 | $this->assert 47 | ->string($parser->getGlobalAnnotation()) 48 | ->isIdenticalTo('@MyHypeAnnot'); 49 | 50 | // Should throw exception on invalid annotation 51 | $this->assert 52 | ->exception(function() use ($parser) { 53 | $parser->setGlobalAnnotation('invalid'); 54 | }) 55 | ->isInstanceOf('\InvalidArgumentException') 56 | ->hasMessage('Boomgo annotation tag should start with "@" character'); 57 | } 58 | 59 | public function testSetGetLocalAnnotation() 60 | { 61 | $parser = new Parser\AnnotationParser(); 62 | 63 | // Should set and get local annotation 64 | $parser->setLocalAnnotation('@MyHypeAnnot'); 65 | 66 | $this->assert 67 | ->string($parser->getLocalAnnotation()) 68 | ->isIdenticalTo('@MyHypeAnnot'); 69 | 70 | // Should throw exception on invalid annotation 71 | $this->assert 72 | ->exception(function() use ($parser) { 73 | $parser->setLocalAnnotation('invalid'); 74 | }) 75 | ->isInstanceOf('\InvalidArgumentException') 76 | ->hasMessage('Boomgo annotation tag should start with "@" character'); 77 | } 78 | 79 | public function testGetExtension() 80 | { 81 | // Should return php as supported extension 82 | $parser = new Parser\AnnotationParser(); 83 | $this->assert 84 | ->string($parser->getExtension()) 85 | ->isEqualTo('php'); 86 | } 87 | 88 | public function testSupports() 89 | { 90 | // Should return true for a php file 91 | $parser = new Parser\AnnotationParser(); 92 | $this->assert 93 | ->boolean($parser->supports(__FILE__)) 94 | ->isTrue(); 95 | 96 | // Should return false for a non php file 97 | $unsupported = __DIR__.DIRECTORY_SEPARATOR.'unsupported'; 98 | touch($unsupported); 99 | 100 | $this->assert 101 | ->boolean($parser->supports($unsupported)) 102 | ->isFalse(); 103 | 104 | unlink($unsupported); 105 | } 106 | 107 | public function testParse() 108 | { 109 | // Should throw exception if no namespace is defined in the file 110 | $parser = new Parser\AnnotationParser(); 111 | $this->assert 112 | ->exception(function() use ($parser) { 113 | $parser->parse(__DIR__.'/../Fixture/NoNamespace.php'); 114 | }) 115 | ->isInstanceOf('RuntimeException') 116 | ->hasMessage('Unable to find namespace or class declaration'); 117 | 118 | // Should throw exception if Boomgo local annotation is not unique per property 119 | $parser = new Parser\AnnotationParser(); 120 | $this->assert 121 | ->exception(function() use ($parser) { 122 | $parser->parse(__DIR__.'/../Fixture/AnnotationInvalidBoomgo.php'); 123 | }) 124 | ->isInstanceOf('RuntimeException') 125 | ->hasMessage('Boomgo annotation tag should occur only once for "Boomgo\Tests\Units\Fixture\AnnotationInvalidBoomgo->invalidAnnotation"'); 126 | 127 | // Should throw exception if a Boomgo property contains more than on @var tag 128 | $parser = new Parser\AnnotationParser(); 129 | $this->assert 130 | ->exception(function() use ($parser) { 131 | $parser->parse(__DIR__.'/../Fixture/AnnotationInvalidVar.php'); 132 | }) 133 | ->isInstanceOf('RuntimeException') 134 | ->hasMessage('"@var" tag is not unique for "Boomgo\Tests\Units\Fixture\AnnotationInvalidVar->invalidVar"'); 135 | 136 | // Should parse an annoted class 137 | $parser = new Parser\AnnotationParser(); 138 | 139 | $metadata = $parser->parse(__DIR__.'/../Fixture/Annotation.php'); 140 | $this->assert 141 | ->array($metadata) 142 | ->hasSize(2) 143 | ->hasKeys(array('class', 'definitions')) 144 | ->string($metadata['class']) 145 | ->isIdenticalTo('Boomgo\\Tests\\Units\\Fixture\\Annotation') 146 | ->array($metadata['definitions']) 147 | ->hasSize(7) 148 | ->hasKeys(array('novar', 'type', 'typeDescription', 'namespace', 'typeNamespace', 'typeManyNamespace', 'typeInvalidNamespace')) 149 | ->array($metadata['definitions']['novar']) 150 | ->hasSize(1) 151 | ->isIdenticalTo(array('attribute' => 'novar')) 152 | ->array($metadata['definitions']['type']) 153 | ->hasSize(2) 154 | ->isIdenticalTo(array('attribute' => 'type', 'type' => 'type')) 155 | ->array($metadata['definitions']['typeDescription']) 156 | ->hasSize(2) 157 | ->isIdenticalTo(array('attribute' => 'typeDescription', 'type' => 'type')) 158 | ->array($metadata['definitions']['namespace']) 159 | ->hasSize(2) 160 | ->isIdenticalTo(array('attribute' => 'namespace', 'type' => 'Type\\Is\\Namespace\\Object')) 161 | ->array($metadata['definitions']['typeNamespace']) 162 | ->hasSize(3) 163 | ->isIdenticalTo(array('attribute' => 'typeNamespace', 'type' => 'type', 'mappedClass' => 'Valid\\Namespace\\Object')) 164 | ->array($metadata['definitions']['typeManyNamespace']) 165 | ->hasSize(3) 166 | ->isIdenticalTo(array('attribute' => 'typeManyNamespace', 'type' => 'type', 'mappedClass' => 'First\\Namespace\\Object Second\\Namespace\\Object')) 167 | ->array($metadata['definitions']['typeInvalidNamespace']) 168 | ->hasSize(2) 169 | ->isIdenticalTo(array('attribute' => 'typeInvalidNamespace', 'type' => 'type')); 170 | } 171 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/Units/Test.php: -------------------------------------------------------------------------------- 1 | 10 | * 11 | * For the full copyright and license information, please view the LICENSE 12 | * file that was distributed with this source code. 13 | */ 14 | 15 | namespace Boomgo\Tests\Units; 16 | 17 | use mageekguy\atoum\test as AtoumTest, 18 | mageekguy\atoum\factory; 19 | 20 | /** 21 | * Rewrite default namespace for atoum 22 | * 23 | * @author David Guyon 24 | */ 25 | abstract class Test extends AtoumTest 26 | { 27 | public function __construct(factory $factory = null) 28 | { 29 | $this->setTestNamespace('\\Tests\\Units\\'); 30 | parent::__construct($factory); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Boomgo/Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Boomgo\\Tests', 'tests'); --------------------------------------------------------------------------------