├── composer.json ├── license.md ├── readme.md └── src ├── DependencyInjection ├── Configuration.php ├── DoctrineDateTimeImmutableTypesExtension.php └── exceptions │ └── DoctrineBundleRequiredException.php └── DoctrineDateTimeImmutableTypesBundle.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vasek-purchart/doctrine-date-time-immutable-types-bundle", 3 | "description": "Bundle integration of Doctrine DateTimeImmutable types for Symfony", 4 | "keywords": ["symfony", "bundle", "doctrine", "type", "datetime", "immutable", "datetimeimmutable", "dbal"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Vašek Purchart", 9 | "email": "me@vasekpurchart.cz", 10 | "homepage": "http://vasekpurchart.cz" 11 | } 12 | ], 13 | "require": { 14 | "doctrine/dbal": "~2.6", 15 | "doctrine/doctrine-bundle": "~1.3", 16 | "symfony/config": "~3.0", 17 | "symfony/dependency-injection": "~3.0", 18 | "symfony/http-kernel": "~3.0", 19 | "symfony/yaml": "~3.0" 20 | }, 21 | "require-dev": { 22 | "consistence/coding-standard": "2.4", 23 | "jakub-onderka/php-console-highlighter": "0.3.2", 24 | "jakub-onderka/php-parallel-lint": "1.0", 25 | "phing/phing": "2.16.1", 26 | "phpunit/phpunit": "6.5.12" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "VasekPurchart\\DoctrineDateTimeImmutableTypesBundle\\": "src" 31 | }, 32 | "classmap": [ 33 | "src" 34 | ] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "VasekPurchart\\DoctrineDateTimeImmutableTypesBundle\\": "tests" 39 | } 40 | }, 41 | "config": { 42 | "bin-dir": "bin" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | This work is published under the MIT license (see full statement below). 5 | If you want to deal (as described below) only with a part of this work, 6 | you have to include this license in the part itself. 7 | 8 | The MIT License (MIT) 9 | --------------------- 10 | 11 | Copyright (c) 2014-2015 Vašek Purchart 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Doctrine DateTimeImmutable Types Bundle 2 | ======================================= 3 | 4 | > In [Doctrine DBAL 2.6](https://github.com/doctrine/dbal/releases/tag/v2.6.0) immutable DateTime types were added, so this bundle no longer uses [custom DateTime types implementation](https://github.com/VasekPurchart/Doctrine-Date-Time-Immutable-Types), but rather offers control, how the immutable types are registered, offering the possibility to replace the original DateTime types. 5 | 6 | > If you cannot upgrade to Doctrine DBAL 2.6 use [1.0 version](https://github.com/VasekPurchart/Doctrine-Date-Time-Immutable-Types-Bundle/tree/1.0) of this bundle, which uses the [`vasek-purchart/doctrine-date-time-immutable-types`](https://github.com/VasekPurchart/Doctrine-Date-Time-Immutable-Types) custom DateTime types implementation. 7 | 8 | ### Why would I want to use immutable types? 9 | 10 | All Doctrine date/time based types are using `DateTime` instances, which are mutable. This can lead to breaking encapsulation and therefore bugs. For two reasons: 11 | 12 | 1) You accidentally modify a date when you are doing some computation on it: 13 | 14 | ```php 15 | createdDate; 36 | } 37 | 38 | } 39 | ``` 40 | 41 | ```php 42 | getCreatedDate()); // 2015-01-01 00:00:00 48 | $logRow->getCreatedDate()->modify('+14 days'); 49 | var_dump($logRow->getCreatedDate()); // 2015-01-15 00:00:00 50 | ``` 51 | 52 | 2) Or you *do* intentionally try to update it, which fails because Doctrine will not see this: 53 | ```php 54 | getRenewDate()->modify('+1 year'); 57 | $entityManager->persist($product); 58 | // no updates will be fired because Doctrine could not detect change 59 | // (objects are compared by identity) 60 | $entityManager->flush(); 61 | ``` 62 | 63 | You can prevent this behaviour by returning a new instance (cloning) or using [`DateTimeImmutable`](http://php.net/manual/en/class.datetimeimmutable.php) (which returns a new instance when modified). 64 | 65 | Configuration 66 | ------------- 67 | 68 | Configuration structure with listed default values: 69 | 70 | ```yaml 71 | # app/config/config.yml 72 | doctrine_date_time_immutable_types: 73 | # Choose under which names the types will be registered. 74 | register: add # One of "add"; "replace" 75 | ``` 76 | 77 | `register` 78 | * `add` - add types as new - suffixed with `_immutable` (e.g. `datetime_immutable`) - this is already done by DBAL from version 2.6 79 | * `replace` - replace the original types `date`, `time`, `datetime`, `datetimetz`, i.e. making them immutable 80 | 81 | Usage 82 | ----- 83 | 84 | If you are using the `replace` option, you don't need to change any property mappings of your entities. 85 | 86 | If you are using the `add` option (default), you only have to suffix your field types with `_immutable`: 87 | 88 | ```php 89 | createdDate; 110 | } 111 | 112 | } 113 | ``` 114 | 115 | ```php 116 | getCreatedDate()); // 2015-01-01 00:00:00 120 | $logRow->getCreatedDate()->modify('+14 days'); 121 | var_dump($logRow->getCreatedDate()); // 2015-01-01 00:00:00 122 | ``` 123 | 124 | Installation 125 | ------------ 126 | 127 | Install package [`vasek-purchart/doctrine-date-time-immutable-types-bundle`](https://packagist.org/packages/vasek-purchart/doctrine-date-time-immutable-types-bundle) with [Composer](https://getcomposer.org/): 128 | 129 | ``` 130 | composer require vasek-purchart/doctrine-date-time-immutable-types-bundle 131 | ``` 132 | 133 | Register the bundle in your application kernel: 134 | ```php 135 | // app/AppKernel.php 136 | public function registerBundles() 137 | { 138 | return array( 139 | // ... 140 | new VasekPurchart\DoctrineDateTimeImmutableTypesBundle\DoctrineDateTimeImmutableTypesBundle(), 141 | ); 142 | } 143 | ``` 144 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | rootNode = $rootNode; 23 | } 24 | 25 | public function getConfigTreeBuilder(): TreeBuilder 26 | { 27 | $treeBuilder = new TreeBuilder(); 28 | $rootNode = $treeBuilder->root($this->rootNode); 29 | 30 | $rootNode 31 | ->children() 32 | ->enumNode(self::PARAMETER_REGISTER) 33 | ->info('Choose under which names the types will be registered.') 34 | ->values([ 35 | self::REGISTER_ADD, 36 | self::REGISTER_REPLACE, 37 | ]) 38 | ->defaultValue(self::REGISTER_ADD) 39 | ->end() 40 | ->end() 41 | ->end(); 42 | 43 | return $treeBuilder; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/DependencyInjection/DoctrineDateTimeImmutableTypesExtension.php: -------------------------------------------------------------------------------- 1 | DateImmutableType::class, 24 | Type::DATETIME_IMMUTABLE => DateTimeImmutableType::class, 25 | Type::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, 26 | Type::TIME_IMMUTABLE => TimeImmutableType::class, 27 | ]; 28 | 29 | public function prepend(ContainerBuilder $container) 30 | { 31 | if (!$container->hasExtension(self::DOCTRINE_BUNDLE_ALIAS)) { 32 | throw new \VasekPurchart\DoctrineDateTimeImmutableTypesBundle\DependencyInjection\DoctrineBundleRequiredException(); 33 | } 34 | 35 | $config = $this->getMergedConfig($container); 36 | $types = []; 37 | 38 | if (in_array($config[Configuration::PARAMETER_REGISTER], [ 39 | Configuration::REGISTER_REPLACE, 40 | ])) { 41 | foreach (self::$types as $name => $type) { 42 | $types[str_replace('_immutable', '', $name)] = $type; 43 | } 44 | } 45 | 46 | if (count($types) === 0) { 47 | return; 48 | } 49 | 50 | $container->loadFromExtension(self::DOCTRINE_BUNDLE_ALIAS, [ 51 | 'dbal' => [ 52 | 'types' => $types, 53 | ], 54 | ]); 55 | } 56 | 57 | /** 58 | * @param mixed[][] $configs 59 | * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container 60 | */ 61 | public function load(array $configs, ContainerBuilder $container) 62 | { 63 | // nothing to do 64 | } 65 | 66 | /** 67 | * @param mixed[] $config 68 | * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container 69 | * @return \VasekPurchart\DoctrineDateTimeImmutableTypesBundle\DependencyInjection\Configuration 70 | */ 71 | public function getConfiguration(array $config, ContainerBuilder $container): Configuration 72 | { 73 | return new Configuration( 74 | $this->getAlias() 75 | ); 76 | } 77 | 78 | /** 79 | * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container 80 | * @return mixed[] 81 | */ 82 | private function getMergedConfig(ContainerBuilder $container) 83 | { 84 | $configs = $container->getExtensionConfig($this->getAlias()); 85 | return $this->processConfiguration($this->getConfiguration([], $container), $configs); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/DependencyInjection/exceptions/DoctrineBundleRequiredException.php: -------------------------------------------------------------------------------- 1 |