ヤミRoot VoidGate
User / IP
:
216.73.216.143
Host / Server
:
146.88.233.70 / dev.loger.cm
System
:
Linux hybrid1120.fr.ns.planethoster.net 3.10.0-957.21.2.el7.x86_64 #1 SMP Wed Jun 5 14:26:44 UTC 2019 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
logercm
/
dev.loger.cm
/
fixtures
/
assert
/
Viewing: doctrine.tar
cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php 0000644 00000020032 15120025731 0016217 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ClearableCache; use Doctrine\Common\Cache\MultiDeleteCache; use Doctrine\Common\Cache\MultiGetCache; use Doctrine\Common\Cache\MultiPutCache; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\DoctrineProvider as SymfonyDoctrineProvider; use function array_key_exists; use function assert; use function count; use function current; use function get_class; use function gettype; use function is_object; use function is_string; use function microtime; use function sprintf; use function strpbrk; use const PHP_VERSION_ID; final class CacheAdapter implements CacheItemPoolInterface { private const RESERVED_CHARACTERS = '{}()/\@:'; /** @var Cache */ private $cache; /** @var array<CacheItem|TypedCacheItem> */ private $deferredItems = []; public static function wrap(Cache $cache): CacheItemPoolInterface { if ($cache instanceof DoctrineProvider && ! $cache->getNamespace()) { return $cache->getPool(); } if ($cache instanceof SymfonyDoctrineProvider && ! $cache->getNamespace()) { $getPool = function () { // phpcs:ignore Squiz.Scope.StaticThisUsage.Found return $this->pool; }; return $getPool->bindTo($cache, SymfonyDoctrineProvider::class)(); } return new self($cache); } private function __construct(Cache $cache) { $this->cache = $cache; } /** @internal */ public function getCache(): Cache { return $this->cache; } /** * {@inheritDoc} */ public function getItem($key): CacheItemInterface { assert(self::validKey($key)); if (isset($this->deferredItems[$key])) { $this->commit(); } $value = $this->cache->fetch($key); if (PHP_VERSION_ID >= 80000) { if ($value !== false) { return new TypedCacheItem($key, $value, true); } return new TypedCacheItem($key, null, false); } if ($value !== false) { return new CacheItem($key, $value, true); } return new CacheItem($key, null, false); } /** * {@inheritDoc} */ public function getItems(array $keys = []): array { if ($this->deferredItems) { $this->commit(); } assert(self::validKeys($keys)); $values = $this->doFetchMultiple($keys); $items = []; if (PHP_VERSION_ID >= 80000) { foreach ($keys as $key) { if (array_key_exists($key, $values)) { $items[$key] = new TypedCacheItem($key, $values[$key], true); } else { $items[$key] = new TypedCacheItem($key, null, false); } } return $items; } foreach ($keys as $key) { if (array_key_exists($key, $values)) { $items[$key] = new CacheItem($key, $values[$key], true); } else { $items[$key] = new CacheItem($key, null, false); } } return $items; } /** * {@inheritDoc} */ public function hasItem($key): bool { assert(self::validKey($key)); if (isset($this->deferredItems[$key])) { $this->commit(); } return $this->cache->contains($key); } public function clear(): bool { $this->deferredItems = []; if (! $this->cache instanceof ClearableCache) { return false; } return $this->cache->deleteAll(); } /** * {@inheritDoc} */ public function deleteItem($key): bool { assert(self::validKey($key)); unset($this->deferredItems[$key]); return $this->cache->delete($key); } /** * {@inheritDoc} */ public function deleteItems(array $keys): bool { foreach ($keys as $key) { assert(self::validKey($key)); unset($this->deferredItems[$key]); } return $this->doDeleteMultiple($keys); } public function save(CacheItemInterface $item): bool { return $this->saveDeferred($item) && $this->commit(); } public function saveDeferred(CacheItemInterface $item): bool { if (! $item instanceof CacheItem && ! $item instanceof TypedCacheItem) { return false; } $this->deferredItems[$item->getKey()] = $item; return true; } public function commit(): bool { if (! $this->deferredItems) { return true; } $now = microtime(true); $itemsCount = 0; $byLifetime = []; $expiredKeys = []; foreach ($this->deferredItems as $key => $item) { $lifetime = ($item->getExpiry() ?? $now) - $now; if ($lifetime < 0) { $expiredKeys[] = $key; continue; } ++$itemsCount; $byLifetime[(int) $lifetime][$key] = $item->get(); } $this->deferredItems = []; switch (count($expiredKeys)) { case 0: break; case 1: $this->cache->delete(current($expiredKeys)); break; default: $this->doDeleteMultiple($expiredKeys); break; } if ($itemsCount === 1) { return $this->cache->save($key, $item->get(), (int) $lifetime); } $success = true; foreach ($byLifetime as $lifetime => $values) { $success = $this->doSaveMultiple($values, $lifetime) && $success; } return $success; } public function __destruct() { $this->commit(); } /** * @param mixed $key */ private static function validKey($key): bool { if (! is_string($key)) { throw new InvalidArgument(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if ($key === '') { throw new InvalidArgument('Cache key length must be greater than zero.'); } if (strpbrk($key, self::RESERVED_CHARACTERS) !== false) { throw new InvalidArgument(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); } return true; } /** * @param mixed[] $keys */ private static function validKeys(array $keys): bool { foreach ($keys as $key) { self::validKey($key); } return true; } /** * @param mixed[] $keys */ private function doDeleteMultiple(array $keys): bool { if ($this->cache instanceof MultiDeleteCache) { return $this->cache->deleteMultiple($keys); } $success = true; foreach ($keys as $key) { $success = $this->cache->delete($key) && $success; } return $success; } /** * @param mixed[] $keys * * @return mixed[] */ private function doFetchMultiple(array $keys): array { if ($this->cache instanceof MultiGetCache) { return $this->cache->fetchMultiple($keys); } $values = []; foreach ($keys as $key) { $value = $this->cache->fetch($key); if (! $value) { continue; } $values[$key] = $value; } return $values; } /** * @param mixed[] $keysAndValues */ private function doSaveMultiple(array $keysAndValues, int $lifetime = 0): bool { if ($this->cache instanceof MultiPutCache) { return $this->cache->saveMultiple($keysAndValues, $lifetime); } $success = true; foreach ($keysAndValues as $key => $value) { $success = $this->cache->save($key, $value, $lifetime) && $success; } return $success; } } cache/lib/Doctrine/Common/Cache/Psr6/CacheItem.php 0000644 00000005006 15120025731 0015541 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use DateInterval; use DateTime; use DateTimeInterface; use Psr\Cache\CacheItemInterface; use TypeError; use function get_class; use function gettype; use function is_int; use function is_object; use function microtime; use function sprintf; final class CacheItem implements CacheItemInterface { /** @var string */ private $key; /** @var mixed */ private $value; /** @var bool */ private $isHit; /** @var float|null */ private $expiry; /** * @internal * * @param mixed $data */ public function __construct(string $key, $data, bool $isHit) { $this->key = $key; $this->value = $data; $this->isHit = $isHit; } public function getKey(): string { return $this->key; } /** * {@inheritDoc} * * @return mixed */ public function get() { return $this->value; } public function isHit(): bool { return $this->isHit; } /** * {@inheritDoc} */ public function set($value): self { $this->value = $value; return $this; } /** * {@inheritDoc} */ public function expiresAt($expiration): self { if ($expiration === null) { $this->expiry = null; } elseif ($expiration instanceof DateTimeInterface) { $this->expiry = (float) $expiration->format('U.u'); } else { throw new TypeError(sprintf( 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', is_object($expiration) ? get_class($expiration) : gettype($expiration) )); } return $this; } /** * {@inheritDoc} */ public function expiresAfter($time): self { if ($time === null) { $this->expiry = null; } elseif ($time instanceof DateInterval) { $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (is_int($time)) { $this->expiry = $time + microtime(true); } else { throw new TypeError(sprintf( 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', is_object($time) ? get_class($time) : gettype($time) )); } return $this; } /** * @internal */ public function getExpiry(): ?float { return $this->expiry; } } cache/lib/Doctrine/Common/Cache/Psr6/DoctrineProvider.php 0000644 00000005666 15120025731 0017215 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Doctrine\Common\Cache\Psr6; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\CacheProvider; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\DoctrineAdapter as SymfonyDoctrineAdapter; use Symfony\Contracts\Service\ResetInterface; use function rawurlencode; /** * This class was copied from the Symfony Framework, see the original copyright * notice above. The code is distributed subject to the license terms in * https://github.com/symfony/symfony/blob/ff0cf61278982539c49e467db9ab13cbd342f76d/LICENSE */ final class DoctrineProvider extends CacheProvider { /** @var CacheItemPoolInterface */ private $pool; public static function wrap(CacheItemPoolInterface $pool): Cache { if ($pool instanceof CacheAdapter) { return $pool->getCache(); } if ($pool instanceof SymfonyDoctrineAdapter) { $getCache = function () { // phpcs:ignore Squiz.Scope.StaticThisUsage.Found return $this->provider; }; return $getCache->bindTo($pool, SymfonyDoctrineAdapter::class)(); } return new self($pool); } private function __construct(CacheItemPoolInterface $pool) { $this->pool = $pool; } /** @internal */ public function getPool(): CacheItemPoolInterface { return $this->pool; } public function reset(): void { if ($this->pool instanceof ResetInterface) { $this->pool->reset(); } $this->setNamespace($this->getNamespace()); } /** * {@inheritdoc} */ protected function doFetch($id) { $item = $this->pool->getItem(rawurlencode($id)); return $item->isHit() ? $item->get() : false; } /** * {@inheritdoc} * * @return bool */ protected function doContains($id) { return $this->pool->hasItem(rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doSave($id, $data, $lifeTime = 0) { $item = $this->pool->getItem(rawurlencode($id)); if (0 < $lifeTime) { $item->expiresAfter($lifeTime); } return $this->pool->save($item->set($data)); } /** * {@inheritdoc} * * @return bool */ protected function doDelete($id) { return $this->pool->deleteItem(rawurlencode($id)); } /** * {@inheritdoc} * * @return bool */ protected function doFlush() { return $this->pool->clear(); } /** * {@inheritdoc} * * @return array|null */ protected function doGetStats() { return null; } } cache/lib/Doctrine/Common/Cache/Psr6/InvalidArgument.php 0000644 00000000421 15120025731 0017004 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use InvalidArgumentException; use Psr\Cache\InvalidArgumentException as PsrInvalidArgumentException; /** * @internal */ final class InvalidArgument extends InvalidArgumentException implements PsrInvalidArgumentException { } cache/lib/Doctrine/Common/Cache/Psr6/TypedCacheItem.php 0000644 00000004173 15120025731 0016553 0 ustar 00 <?php namespace Doctrine\Common\Cache\Psr6; use DateInterval; use DateTime; use DateTimeInterface; use Psr\Cache\CacheItemInterface; use TypeError; use function get_debug_type; use function is_int; use function microtime; use function sprintf; final class TypedCacheItem implements CacheItemInterface { private ?float $expiry = null; /** * @internal */ public function __construct( private string $key, private mixed $value, private bool $isHit, ) { } public function getKey(): string { return $this->key; } public function get(): mixed { return $this->value; } public function isHit(): bool { return $this->isHit; } public function set(mixed $value): static { $this->value = $value; return $this; } /** * {@inheritDoc} */ public function expiresAt($expiration): static { if ($expiration === null) { $this->expiry = null; } elseif ($expiration instanceof DateTimeInterface) { $this->expiry = (float) $expiration->format('U.u'); } else { throw new TypeError(sprintf( 'Expected $expiration to be an instance of DateTimeInterface or null, got %s', get_debug_type($expiration) )); } return $this; } /** * {@inheritDoc} */ public function expiresAfter($time): static { if ($time === null) { $this->expiry = null; } elseif ($time instanceof DateInterval) { $this->expiry = microtime(true) + DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); } elseif (is_int($time)) { $this->expiry = $time + microtime(true); } else { throw new TypeError(sprintf( 'Expected $time to be either an integer, an instance of DateInterval or null, got %s', get_debug_type($time) )); } return $this; } /** * @internal */ public function getExpiry(): ?float { return $this->expiry; } } cache/lib/Doctrine/Common/Cache/Cache.php 0000644 00000005357 15120025731 0014101 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers. * * @link www.doctrine-project.org */ interface Cache { public const STATS_HITS = 'hits'; public const STATS_MISSES = 'misses'; public const STATS_UPTIME = 'uptime'; public const STATS_MEMORY_USAGE = 'memory_usage'; public const STATS_MEMORY_AVAILABLE = 'memory_available'; /** * Only for backward compatibility (may be removed in next major release) * * @deprecated */ public const STATS_MEMORY_AVAILIABLE = 'memory_available'; /** * Fetches an entry from the cache. * * @param string $id The id of the cache entry to fetch. * * @return mixed The cached data or FALSE, if no cache entry exists for the given id. */ public function fetch($id); /** * Tests if an entry exists in the cache. * * @param string $id The cache id of the entry to check for. * * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ public function contains($id); /** * Puts data into the cache. * * If a cache entry with the given id already exists, its data will be replaced. * * @param string $id The cache id. * @param mixed $data The cache entry/data. * @param int $lifeTime The lifetime in number of seconds for this cache entry. * If zero (the default), the entry never expires (although it may be deleted from the cache * to make place for other entries). * * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ public function save($id, $data, $lifeTime = 0); /** * Deletes a cache entry. * * @param string $id The cache id. * * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. * Deleting a non-existing entry is considered successful. */ public function delete($id); /** * Retrieves cached information from the data store. * * The server's statistics array has the following values: * * - <b>hits</b> * Number of keys that have been requested and found present. * * - <b>misses</b> * Number of items that have been requested and not found. * * - <b>uptime</b> * Time that the server is running. * * - <b>memory_usage</b> * Memory used by this server to store items. * * - <b>memory_available</b> * Memory allowed to use for storage. * * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. */ public function getStats(); } cache/lib/Doctrine/Common/Cache/CacheProvider.php 0000644 00000020433 15120025731 0015604 0 ustar 00 <?php namespace Doctrine\Common\Cache; use function array_combine; use function array_key_exists; use function array_map; use function sprintf; /** * Base class for cache provider implementations. */ abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiOperationCache { public const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; /** * The namespace to prefix all cache ids with. * * @var string */ private $namespace = ''; /** * The namespace version. * * @var int|null */ private $namespaceVersion; /** * Sets the namespace to prefix all cache ids with. * * @param string $namespace * * @return void */ public function setNamespace($namespace) { $this->namespace = (string) $namespace; $this->namespaceVersion = null; } /** * Retrieves the namespace that prefixes all cache ids. * * @return string */ public function getNamespace() { return $this->namespace; } /** * {@inheritdoc} */ public function fetch($id) { return $this->doFetch($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function fetchMultiple(array $keys) { if (empty($keys)) { return []; } // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys $namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys)); $items = $this->doFetchMultiple($namespacedKeys); $foundItems = []; // no internal array function supports this sort of mapping: needs to be iterative // this filters and combines keys in one pass foreach ($namespacedKeys as $requestedKey => $namespacedKey) { if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) { continue; } $foundItems[$requestedKey] = $items[$namespacedKey]; } return $foundItems; } /** * {@inheritdoc} */ public function saveMultiple(array $keysAndValues, $lifetime = 0) { $namespacedKeysAndValues = []; foreach ($keysAndValues as $key => $value) { $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; } return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); } /** * {@inheritdoc} */ public function contains($id) { return $this->doContains($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function save($id, $data, $lifeTime = 0) { return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); } /** * {@inheritdoc} */ public function deleteMultiple(array $keys) { return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys)); } /** * {@inheritdoc} */ public function delete($id) { return $this->doDelete($this->getNamespacedId($id)); } /** * {@inheritdoc} */ public function getStats() { return $this->doGetStats(); } /** * {@inheritDoc} */ public function flushAll() { return $this->doFlush(); } /** * {@inheritDoc} */ public function deleteAll() { $namespaceCacheKey = $this->getNamespaceCacheKey(); $namespaceVersion = $this->getNamespaceVersion() + 1; if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { $this->namespaceVersion = $namespaceVersion; return true; } return false; } /** * Prefixes the passed id with the configured namespace value. * * @param string $id The id to namespace. * * @return string The namespaced id. */ private function getNamespacedId(string $id): string { $namespaceVersion = $this->getNamespaceVersion(); return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); } /** * Returns the namespace cache key. */ private function getNamespaceCacheKey(): string { return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); } /** * Returns the namespace version. */ private function getNamespaceVersion(): int { if ($this->namespaceVersion !== null) { return $this->namespaceVersion; } $namespaceCacheKey = $this->getNamespaceCacheKey(); $this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1; return $this->namespaceVersion; } /** * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. * * @param string[] $keys Array of keys to retrieve from cache * * @return mixed[] Array of values retrieved for the given keys. */ protected function doFetchMultiple(array $keys) { $returnValues = []; foreach ($keys as $key) { $item = $this->doFetch($key); if ($item === false && ! $this->doContains($key)) { continue; } $returnValues[$key] = $item; } return $returnValues; } /** * Fetches an entry from the cache. * * @param string $id The id of the cache entry to fetch. * * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. */ abstract protected function doFetch($id); /** * Tests if an entry exists in the cache. * * @param string $id The cache id of the entry to check for. * * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ abstract protected function doContains($id); /** * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. * * @param mixed[] $keysAndValues Array of keys and values to save in cache * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these * cache entries (0 => infinite lifeTime). * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) { $success = true; foreach ($keysAndValues as $key => $value) { if ($this->doSave($key, $value, $lifetime)) { continue; } $success = false; } return $success; } /** * Puts data into the cache. * * @param string $id The cache id. * @param string $data The cache entry/data. * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this * cache entry (0 => infinite lifeTime). * * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ abstract protected function doSave($id, $data, $lifeTime = 0); /** * Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it. * * @param string[] $keys Array of keys to delete from cache * * @return bool TRUE if the operation was successful, FALSE if it wasn't */ protected function doDeleteMultiple(array $keys) { $success = true; foreach ($keys as $key) { if ($this->doDelete($key)) { continue; } $success = false; } return $success; } /** * Deletes a cache entry. * * @param string $id The cache id. * * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. */ abstract protected function doDelete($id); /** * Flushes all cache entries. * * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. */ abstract protected function doFlush(); /** * Retrieves cached information from the data store. * * @return mixed[]|null An associative array with server's statistics if available, NULL otherwise. */ abstract protected function doGetStats(); } cache/lib/Doctrine/Common/Cache/ClearableCache.php 0000644 00000000765 15120025731 0015672 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache that can be flushed. * * Intended to be used for partial clearing of a cache namespace. For a more * global "flushing", see {@see FlushableCache}. * * @link www.doctrine-project.org */ interface ClearableCache { /** * Deletes all cache entries in the current cache namespace. * * @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise. */ public function deleteAll(); } cache/lib/Doctrine/Common/Cache/FlushableCache.php 0000644 00000000536 15120025731 0015721 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache that can be flushed. * * @link www.doctrine-project.org */ interface FlushableCache { /** * Flushes all cache entries, globally. * * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. */ public function flushAll(); } cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php 0000644 00000000722 15120025731 0016226 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to put many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiDeleteCache { /** * Deletes several cache entries. * * @param string[] $keys Array of keys to delete from cache * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ public function deleteMultiple(array $keys); } cache/lib/Doctrine/Common/Cache/MultiGetCache.php 0000644 00000001121 15120025731 0015535 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to get many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiGetCache { /** * Returns an associative array of values for keys is found in cache. * * @param string[] $keys Array of keys to retrieve from cache * * @return mixed[] Array of retrieved values, indexed by the specified keys. * Values that couldn't be retrieved are not contained in this array. */ public function fetchMultiple(array $keys); } cache/lib/Doctrine/Common/Cache/MultiOperationCache.php 0000644 00000000373 15120025731 0016766 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that supports multiple items manipulation. * * @link www.doctrine-project.org */ interface MultiOperationCache extends MultiGetCache, MultiDeleteCache, MultiPutCache { } cache/lib/Doctrine/Common/Cache/MultiPutCache.php 0000644 00000001300 15120025731 0015565 0 ustar 00 <?php namespace Doctrine\Common\Cache; /** * Interface for cache drivers that allows to put many items at once. * * @deprecated * * @link www.doctrine-project.org */ interface MultiPutCache { /** * Returns a boolean value indicating if the operation succeeded. * * @param mixed[] $keysAndValues Array of keys and values to save in cache * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these * cache entries (0 => infinite lifeTime). * * @return bool TRUE if the operation was successful, FALSE if it wasn't. */ public function saveMultiple(array $keysAndValues, $lifetime = 0); } cache/LICENSE 0000644 00000002051 15120025731 0006606 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cache/README.md 0000644 00000001617 15120025731 0007067 0 ustar 00 # Doctrine Cache [](https://github.com/doctrine/cache/actions) [](https://codecov.io/gh/doctrine/cache/branch/1.10.x) [](https://packagist.org/packages/doctrine/cache) [](https://packagist.org/packages/doctrine/cache) Cache component extracted from the Doctrine Common project. [Documentation](https://www.doctrine-project.org/projects/doctrine-cache/en/current/index.html) This library is deprecated and will no longer receive bug fixes from the Doctrine Project. Please use a different cache library, preferably PSR-6 or PSR-16 instead. cache/UPGRADE-1.11.md 0000644 00000002362 15120025731 0007575 0 ustar 00 # Upgrade to 1.11 doctrine/cache will no longer be maintained and all cache implementations have been marked as deprecated. These implementations will be removed in 2.0, which will only contain interfaces to provide a lightweight package for backward compatibility. There are two new classes to use in the `Doctrine\Common\Cache\Psr6` namespace: * The `CacheAdapter` class allows using any Doctrine Cache as PSR-6 cache. This is useful to provide a forward compatibility layer in libraries that accept Doctrine cache implementations and switch to PSR-6. * The `DoctrineProvider` class allows using any PSR-6 cache as Doctrine cache. This implementation is designed for libraries that leak the cache and want to switch to allowing PSR-6 implementations. This class is design to be used during the transition phase of sunsetting doctrine/cache support. A full example to setup a filesystem based PSR-6 cache with symfony/cache using the `DoctrineProvider` to convert back to Doctrine's `Cache` interface: ```php use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Symfony\Component\Cache\Adapter\FilesystemAdapter; $cachePool = new FilesystemAdapter(); $cache = DoctrineProvider::wrap($cachePool); // $cache instanceof \Doctrine\Common\Cache\Cache ``` cache/UPGRADE-1.4.md 0000644 00000001326 15120025731 0007516 0 ustar 00 # Upgrade to 1.4 ## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`. If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding the property in your own implementation. ## Minor BC Break: file based caches paths changed `Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and `Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure. If you rely on warmed up caches for deployments, consider that caches generated with `doctrine/cache` `<1.4` are not compatible with the new directory structure, and will be ignored. cache/composer.json 0000644 00000003152 15120025731 0010326 0 ustar 00 { "name": "doctrine/cache", "type": "library", "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", "keywords": [ "php", "cache", "caching", "abstraction", "redis", "memcached", "couchdb", "xcache", "apcu" ], "homepage": "https://www.doctrine-project.org/projects/cache.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} ], "require": { "php": "~7.1 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "doctrine/coding-standard": "^9", "psr/cache": "^1.0 || ^2.0 || ^3.0", "cache/integration-tests": "dev-master", "symfony/cache": "^4.4 || ^5.4 || ^6", "symfony/var-exporter": "^4.4 || ^5.4 || ^6" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php 0000644 00000003663 15120025731 0021772 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Deprecations\PHPUnit; use Doctrine\Deprecations\Deprecation; use function sprintf; trait VerifyDeprecations { /** @var array<string,int> */ private $doctrineDeprecationsExpectations = []; /** @var array<string,int> */ private $doctrineNoDeprecationsExpectations = []; public function expectDeprecationWithIdentifier(string $identifier): void { $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } public function expectNoDeprecationWithIdentifier(string $identifier): void { $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } /** * @before */ public function enableDeprecationTracking(): void { Deprecation::enableTrackingDeprecations(); } /** * @after */ public function verifyDeprecationsAreTriggered(): void { foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount > $expectation, sprintf( "Expected deprecation with identifier '%s' was not triggered by code executed in test.", $identifier ) ); } foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount === $expectation, sprintf( "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", $identifier ) ); } } } deprecations/lib/Doctrine/Deprecations/Deprecation.php 0000644 00000022371 15120025731 0017130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Deprecations; use Psr\Log\LoggerInterface; use function array_key_exists; use function array_reduce; use function assert; use function debug_backtrace; use function sprintf; use function strpos; use function strrpos; use function substr; use function trigger_error; use const DEBUG_BACKTRACE_IGNORE_ARGS; use const DIRECTORY_SEPARATOR; use const E_USER_DEPRECATED; /** * Manages Deprecation logging in different ways. * * By default triggered exceptions are not logged. * * To enable different deprecation logging mechanisms you can call the * following methods: * * - Minimal collection of deprecations via getTriggeredDeprecations() * \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); * * - Uses @trigger_error with E_USER_DEPRECATED * \Doctrine\Deprecations\Deprecation::enableWithTriggerError(); * * - Sends deprecation messages via a PSR-3 logger * \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); * * Packages that trigger deprecations should use the `trigger()` or * `triggerIfCalledFromOutside()` methods. */ class Deprecation { private const TYPE_NONE = 0; private const TYPE_TRACK_DEPRECATIONS = 1; private const TYPE_TRIGGER_ERROR = 2; private const TYPE_PSR_LOGGER = 4; /** @var int-mask-of<self::TYPE_*>|null */ private static $type; /** @var LoggerInterface|null */ private static $logger; /** @var array<string,bool> */ private static $ignoredPackages = []; /** @var array<string,int> */ private static $triggeredDeprecations = []; /** @var array<string,bool> */ private static $ignoredLinks = []; /** @var bool */ private static $deduplication = true; /** * Trigger a deprecation for the given package and identfier. * * The link should point to a Github issue or Wiki entry detailing the * deprecation. It is additionally used to de-duplicate the trigger of the * same deprecation during a request. * * @param float|int|string $args */ public static function trigger(string $package, string $link, string $message, ...$args): void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * Trigger a deprecation for the given package and identifier when called from outside. * * "Outside" means we assume that $package is currently installed as a * dependency and the caller is not a file in that package. When $package * is installed as a root package then deprecations triggered from the * tests folder are also considered "outside". * * This deprecation method assumes that you are using Composer to install * the dependency and are using the default /vendor/ folder and not a * Composer plugin to change the install location. The assumption is also * that $package is the exact composer packge name. * * Compared to {@link trigger()} this method causes some overhead when * deprecation tracking is enabled even during deduplication, because it * needs to call {@link debug_backtrace()} * * @param float|int|string $args */ public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void { $type = self::$type ?? self::getTypeFromEnv(); if ($type === self::TYPE_NONE) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); // first check that the caller is not from a tests folder, in which case we always let deprecations pass if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; if (strpos($backtrace[0]['file'], $path) === false) { return; } if (strpos($backtrace[1]['file'], $path) !== false) { return; } } if (isset(self::$ignoredLinks[$link])) { return; } if (array_key_exists($link, self::$triggeredDeprecations)) { self::$triggeredDeprecations[$link]++; } else { self::$triggeredDeprecations[$link] = 1; } if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * @param list<array{function: string, line?: int, file?: string, class?: class-string, type?: string, args?: mixed[], object?: object}> $backtrace */ private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void { $type = self::$type ?? self::getTypeFromEnv(); if (($type & self::TYPE_PSR_LOGGER) > 0) { $context = [ 'file' => $backtrace[0]['file'] ?? null, 'line' => $backtrace[0]['line'] ?? null, 'package' => $package, 'link' => $link, ]; assert(self::$logger !== null); self::$logger->notice($message, $context); } if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) { return; } $message .= sprintf( ' (%s:%d called by %s:%d, %s, package %s)', self::basename($backtrace[0]['file'] ?? 'native code'), $backtrace[0]['line'] ?? 0, self::basename($backtrace[1]['file'] ?? 'native code'), $backtrace[1]['line'] ?? 0, $link, $package ); @trigger_error($message, E_USER_DEPRECATED); } /** * A non-local-aware version of PHPs basename function. */ private static function basename(string $filename): string { $pos = strrpos($filename, DIRECTORY_SEPARATOR); if ($pos === false) { return $filename; } return substr($filename, $pos + 1); } public static function enableTrackingDeprecations(): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRACK_DEPRECATIONS; } public static function enableWithTriggerError(): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_TRIGGER_ERROR; } public static function enableWithPsrLogger(LoggerInterface $logger): void { self::$type = self::$type ?? 0; self::$type |= self::TYPE_PSR_LOGGER; self::$logger = $logger; } public static function withoutDeduplication(): void { self::$deduplication = false; } public static function disable(): void { self::$type = self::TYPE_NONE; self::$logger = null; self::$deduplication = true; self::$ignoredLinks = []; foreach (self::$triggeredDeprecations as $link => $count) { self::$triggeredDeprecations[$link] = 0; } } public static function ignorePackage(string $packageName): void { self::$ignoredPackages[$packageName] = true; } public static function ignoreDeprecations(string ...$links): void { foreach ($links as $link) { self::$ignoredLinks[$link] = true; } } public static function getUniqueTriggeredDeprecationsCount(): int { return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) { return $carry + $count; }, 0); } /** * Returns each triggered deprecation link identifier and the amount of occurrences. * * @return array<string,int> */ public static function getTriggeredDeprecations(): array { return self::$triggeredDeprecations; } /** * @return int-mask-of<self::TYPE_*> */ private static function getTypeFromEnv(): int { switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) { case 'trigger': self::$type = self::TYPE_TRIGGER_ERROR; break; case 'track': self::$type = self::TYPE_TRACK_DEPRECATIONS; break; default: self::$type = self::TYPE_NONE; break; } return self::$type; } } deprecations/LICENSE 0000644 00000002051 15120025731 0010223 0 ustar 00 Copyright (c) 2020-2021 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. deprecations/README.md 0000644 00000010444 15120025731 0010502 0 ustar 00 # Doctrine Deprecations A small (side-effect free by default) layer on top of `trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. - no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under - options to avoid having to rely on error handlers global state by using PSR-3 logging - deduplicate deprecation messages to avoid excessive triggering and reduce overhead We recommend to collect Deprecations using a PSR logger instead of relying on the global error handler. ## Usage from consumer perspective: Enable Doctrine deprecations to be sent to a PSR3 logger: ```php \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); ``` Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`. Alternatively, call: ```php \Doctrine\Deprecations\Deprecation::enableWithTriggerError(); ``` If you only want to enable deprecation tracking, without logging or calling `trigger_error` then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`. Alternatively, call: ```php \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); ``` Tracking is enabled with all three modes and provides access to all triggered deprecations and their individual count: ```php $deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); foreach ($deprecations as $identifier => $count) { echo $identifier . " was triggered " . $count . " times\n"; } ``` ### Suppressing Specific Deprecations Disable triggering about specific deprecations: ```php \Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); ``` Disable all deprecations from a package ```php \Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); ``` ### Other Operations When used within PHPUnit or other tools that could collect multiple instances of the same deprecations the deduplication can be disabled: ```php \Doctrine\Deprecations\Deprecation::withoutDeduplication(); ``` Disable deprecation tracking again: ```php \Doctrine\Deprecations\Deprecation::disable(); ``` ## Usage from a library/producer perspective: When you want to unconditionally trigger a deprecation even when called from the library itself then the `trigger` method is the way to go: ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` If variable arguments are provided at the end, they are used with `sprintf` on the message. ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://github.com/doctrine/orm/issue/1234", "message %s %d", "foo", 1234 ); ``` When you want to trigger a deprecation only when it is called by a function outside of the current package, but not trigger when the package itself is the cause, then use: ```php \Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` Based on the issue link each deprecation message is only triggered once per request. A limited stacktrace is included in the deprecation message to find the offending location. Note: A producer/library should never call `Deprecation::enableWith` methods and leave the decision how to handle deprecations to application and frameworks. ## Usage in PHPUnit tests There is a `VerifyDeprecations` trait that you can use to make assertions on the occurrence of deprecations within a test. ```php use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; class MyTest extends TestCase { use VerifyDeprecations; public function testSomethingDeprecation() { $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); triggerTheCodeWithDeprecation(); } public function testSomethingDeprecationFixed() { $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); triggerTheCodeWithoutDeprecation(); } } ``` ## What is a deprecation identifier? An identifier for deprecations is just a link to any resource, most often a Github Issue or Pull Request explaining the deprecation and potentially its alternative. deprecations/composer.json 0000644 00000002311 15120025731 0011737 0 ustar 00 { "name": "doctrine/deprecations", "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "license": "MIT", "type": "library", "homepage": "https://www.doctrine-project.org/", "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^9", "phpstan/phpstan": "1.4.10 || 1.10.15", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "psalm/plugin-phpunit": "0.18.4", "psr/log": "^1 || ^2 || ^3", "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, "autoload": { "psr-4": { "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" } }, "autoload-dev": { "psr-4": { "DeprecationTests\\": "test_fixtures/src", "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } deprecations/phpcs.xml 0000644 00000001212 15120025731 0011053 0 ustar 00 <?xml version="1.0"?> <ruleset> <arg name="basepath" value="."/> <arg name="extensions" value="php"/> <arg name="parallel" value="80"/> <arg name="cache" value=".phpcs-cache"/> <arg name="colors"/> <!-- Ignore warnings, show progress of the run and show sniff names --> <arg value="nps"/> <config name="php_version" value="70100"/> <!-- Directories to be checked --> <file>lib</file> <file>tests</file> <!-- Include full Doctrine Coding Standard --> <rule ref="Doctrine"> <exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" /> </rule> </ruleset> deprecations/phpstan.neon 0000644 00000000261 15120025731 0011555 0 ustar 00 parameters: level: 6 paths: - lib - tests includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon deprecations/psalm.xml 0000644 00000002102 15120025731 0011051 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="1" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" findUnusedBaselineEntry="true" findUnusedCode="false" > <projectFiles> <directory name="lib/Doctrine/Deprecations" /> <directory name="tests/Doctrine/Deprecations" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> <plugins> <pluginClass class="Psalm\PhpUnitPlugin\Plugin"/> </plugins> <issueHandlers> <DeprecatedMethod> <errorLevel type="suppress"> <!-- Remove when dropping support for PHPUnit 9.6 --> <referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecation"/> <referencedMethod name="PHPUnit\Framework\TestCase::expectDeprecationMessage"/> </errorLevel> </DeprecatedMethod> </issueHandlers> </psalm> collections/docs/en/derived-collections.rst 0000644 00000001342 15120025732 0015101 0 ustar 00 Derived Collections =================== You can create custom collection classes by extending the ``Doctrine\Common\Collections\ArrayCollection`` class. If the ``__construct`` semantics are different from the default ``ArrayCollection`` you can override the ``createFrom`` method: .. code-block:: php final class DerivedArrayCollection extends ArrayCollection { /** @var \stdClass */ private $foo; public function __construct(\stdClass $foo, array $elements = []) { $this->foo = $foo; parent::__construct($elements); } protected function createFrom(array $elements) : self { return new static($this->foo, $elements); } } collections/docs/en/expression-builder.rst 0000644 00000006726 15120025732 0015001 0 ustar 00 Expression Builder ================== The Expression Builder is a convenient fluent interface for building expressions to be used with the ``Doctrine\Common\Collections\Criteria`` class: .. code-block:: php $expressionBuilder = Criteria::expr(); $criteria = new Criteria(); $criteria->where($expressionBuilder->eq('name', 'jwage')); $criteria->orWhere($expressionBuilder->eq('name', 'romanb')); $collection->matching($criteria); The ``ExpressionBuilder`` has the following API: andX ---- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->andX( $expressionBuilder->eq('foo', 1), $expressionBuilder->eq('bar', 1) ); $collection->matching(new Criteria($expression)); orX --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->orX( $expressionBuilder->eq('foo', 1), $expressionBuilder->eq('bar', 1) ); $collection->matching(new Criteria($expression)); eq --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->eq('foo', 1); $collection->matching(new Criteria($expression)); gt --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->gt('foo', 1); $collection->matching(new Criteria($expression)); lt --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->lt('foo', 1); $collection->matching(new Criteria($expression)); gte --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->gte('foo', 1); $collection->matching(new Criteria($expression)); lte --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->lte('foo', 1); $collection->matching(new Criteria($expression)); neq --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->neq('foo', 1); $collection->matching(new Criteria($expression)); isNull ------ .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->isNull('foo'); $collection->matching(new Criteria($expression)); in --- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->in('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); notIn ----- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->notIn('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); contains -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->contains('foo', 'value1'); $collection->matching(new Criteria($expression)); memberOf -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->memberOf('foo', ['value1', 'value2']); $collection->matching(new Criteria($expression)); startsWith ---------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->startsWith('foo', 'hello'); $collection->matching(new Criteria($expression)); endsWith -------- .. code-block:: php $expressionBuilder = Criteria::expr(); $expression = $expressionBuilder->endsWith('foo', 'world'); $collection->matching(new Criteria($expression)); collections/docs/en/expressions.rst 0000644 00000004263 15120025732 0013532 0 ustar 00 Expressions =========== The ``Doctrine\Common\Collections\Expr\Comparison`` class can be used to create expressions to be used with the ``Doctrine\Common\Collections\Criteria`` class. It has the following operator constants: - ``Comparison::EQ`` - ``Comparison::NEQ`` - ``Comparison::LT`` - ``Comparison::LTE`` - ``Comparison::GT`` - ``Comparison::GTE`` - ``Comparison::IS`` - ``Comparison::IN`` - ``Comparison::NIN`` - ``Comparison::CONTAINS`` - ``Comparison::MEMBER_OF`` - ``Comparison::STARTS_WITH`` - ``Comparison::ENDS_WITH`` The ``Doctrine\Common\Collections\Criteria`` class has the following API to be used with expressions: where ----- Sets the where expression to evaluate when this Criteria is searched for. .. code-block:: php $expr = new Comparison('key', Comparison::EQ, 'value'); $criteria->where($expr); andWhere -------- Appends the where expression to evaluate when this Criteria is searched for using an AND with previous expression. .. code-block:: php $expr = new Comparison('key', Comparison::EQ, 'value'); $criteria->andWhere($expr); orWhere ------- Appends the where expression to evaluate when this Criteria is searched for using an OR with previous expression. .. code-block:: php $expr1 = new Comparison('key', Comparison::EQ, 'value1'); $expr2 = new Comparison('key', Comparison::EQ, 'value2'); $criteria->where($expr1); $criteria->orWhere($expr2); orderBy ------- Sets the ordering of the result of this Criteria. .. code-block:: php $criteria->orderBy(['name' => Criteria::ASC]); setFirstResult -------------- Set the number of first result that this Criteria should return. .. code-block:: php $criteria->setFirstResult(0); getFirstResult -------------- Gets the current first result option of this Criteria. .. code-block:: php $criteria->setFirstResult(10); echo $criteria->getFirstResult(); // 10 setMaxResults ------------- Sets the max results that this Criteria should return. .. code-block:: php $criteria->setMaxResults(20); getMaxResults ------------- Gets the current max results option of this Criteria. .. code-block:: php $criteria->setMaxResults(20); echo $criteria->getMaxResults(); // 20 collections/docs/en/index.rst 0000644 00000020323 15120025732 0012252 0 ustar 00 Introduction ============ Doctrine Collections is a library that contains classes for working with arrays of data. Here is an example using the simple ``Doctrine\Common\Collections\ArrayCollection`` class: .. code-block:: php <?php use Doctrine\Common\Collections\ArrayCollection; $collection = new ArrayCollection([1, 2, 3]); $filteredCollection = $collection->filter(function($element) { return $element > 1; }); // [2, 3] Collection Methods ================== Doctrine Collections provides an interface named ``Doctrine\Common\Collections\Collection`` that resembles the nature of a regular PHP array. That is, it is essentially an **ordered map** that can also be used like a list. A Collection has an internal iterator just like a PHP array. In addition, a Collection can be iterated with external iterators, which is preferable. To use an external iterator simply use the foreach language construct to iterate over the collection, which calls ``getIterator()`` internally, or explicitly retrieve an iterator though ``getIterator()`` which can then be used to iterate over the collection. You can not rely on the internal iterator of the collection being at a certain position unless you explicitly positioned it before. Methods that do not alter the collection or have template types appearing in invariant or contravariant positions are not directly defined in ``Doctrine\Common\Collections\Collection``, but are inherited from the ``Doctrine\Common\Collections\ReadableCollection`` interface. The methods available on the interface are: add --- Adds an element at the end of the collection. .. code-block:: php $collection->add('test'); clear ----- Clears the collection, removing all elements. .. code-block:: php $collection->clear(); contains -------- Checks whether an element is contained in the collection. This is an O(n) operation, where n is the size of the collection. .. code-block:: php $collection = new Collection(['test']); $contains = $collection->contains('test'); // true containsKey ----------- Checks whether the collection contains an element with the specified key/index. .. code-block:: php $collection = new Collection(['test' => true]); $contains = $collection->containsKey('test'); // true current ------- Gets the element of the collection at the current iterator position. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $current = $collection->current(); // first get --- Gets the element at the specified key/index. .. code-block:: php $collection = new Collection([ 'key' => 'value', ]); $value = $collection->get('key'); // value getKeys ------- Gets all keys/indices of the collection. .. code-block:: php $collection = new Collection(['a', 'b', 'c']); $keys = $collection->getKeys(); // [0, 1, 2] getValues --------- Gets all values of the collection. .. code-block:: php $collection = new Collection([ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', ]); $values = $collection->getValues(); // ['value1', 'value2', 'value3'] isEmpty ------- Checks whether the collection is empty (contains no elements). .. code-block:: php $collection = new Collection(['a', 'b', 'c']); $isEmpty = $collection->isEmpty(); // false first ----- Sets the internal iterator to the first element in the collection and returns this element. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $first = $collection->first(); // first exists ------ Tests for the existence of an element that satisfies the given predicate. .. code-block:: php $collection = new Collection(['first', 'second', 'third']); $exists = $collection->exists(function($key, $value) { return $value === 'first'; }); // true filter ------ Returns all the elements of this collection for which your callback function returns `true`. The order and keys of the elements are preserved. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $filteredCollection = $collection->filter(function($element) { return $element > 1; }); // [2, 3] forAll ------ Tests whether the given predicate holds for all elements of this collection. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $forAll = $collection->forAll(function($key, $value) { return $value > 1; }); // false indexOf ------- Gets the index/key of a given element. The comparison of two elements is strict, that means not only the value but also the type must match. For objects this means reference equality. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $indexOf = $collection->indexOf(3); // 2 key --- Gets the key/index of the element at the current iterator position. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->next(); $key = $collection->key(); // 1 last ---- Sets the internal iterator to the last element in the collection and returns this element. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $last = $collection->last(); // 3 map --- Applies the given function to each element in the collection and returns a new collection with the elements returned by the function. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $mappedCollection = $collection->map(function($value) { return $value + 1; }); // [2, 3, 4] next ---- Moves the internal iterator position to the next element and returns this element. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $next = $collection->next(); // 2 partition --------- Partitions this collection in two collections according to a predicate. Keys are preserved in the resulting collections. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $mappedCollection = $collection->partition(function($key, $value) { return $value > 1 }); // [[2, 3], [1]] remove ------ Removes the element at the specified index from the collection. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->remove(0); // [2, 3] removeElement ------------- Removes the specified element from the collection, if it is found. .. code-block:: php $collection = new ArrayCollection([1, 2, 3]); $collection->removeElement(3); // [1, 2] set --- Sets an element in the collection at the specified key/index. .. code-block:: php $collection = new ArrayCollection(); $collection->set('name', 'jwage'); slice ----- Extracts a slice of $length elements starting at position $offset from the Collection. If $length is null it returns all elements from $offset to the end of the Collection. Keys have to be preserved by this method. Calling this method will only return the selected slice and NOT change the elements contained in the collection slice is called on. .. code-block:: php $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); $slice = $collection->slice(1, 2); // [1, 2] toArray ------- Gets a native PHP array representation of the collection. .. code-block:: php $collection = new ArrayCollection([0, 1, 2, 3, 4, 5]); $array = $collection->toArray(); // [0, 1, 2, 3, 4, 5] Selectable Methods ================== Some Doctrine Collections, like ``Doctrine\Common\Collections\ArrayCollection``, implement an interface named ``Doctrine\Common\Collections\Selectable`` that offers the usage of a powerful expressions API, where conditions can be applied to a collection to get a result with matching elements only. matching -------- Selects all elements from a selectable that match the expression and returns a new collection containing these elements. .. code-block:: php use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; $collection = new ArrayCollection([ [ 'name' => 'jwage', ], [ 'name' => 'romanb', ], ]); $expr = new Comparison('name', '=', 'jwage'); $criteria = new Criteria(); $criteria->where($expr); $matched = $collection->matching($criteria); // ['jwage'] You can read more about expressions :ref:`here <expressions>`. collections/docs/en/lazy-collections.rst 0000644 00000001344 15120025732 0014440 0 ustar 00 Lazy Collections ================ To create a lazy collection you can extend the ``Doctrine\Common\Collections\AbstractLazyCollection`` class and define the ``doInitialize`` method. Here is an example where we lazily query the database for a collection of user records: .. code-block:: php use Doctrine\DBAL\Connection; class UsersLazyCollection extends AbstractLazyCollection { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } protected function doInitialize() : void { $this->collection = $this->connection->fetchAll('SELECT * FROM users'); } } collections/docs/en/sidebar.rst 0000644 00000000172 15120025732 0012554 0 ustar 00 .. toctree:: :depth: 3 index expressions expression-builder derived-collections lazy-collections collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php 0000644 00000020661 15120025732 0023612 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; use ArrayAccess; use Closure; use RuntimeException; use function explode; use function in_array; use function is_array; use function is_scalar; use function iterator_to_array; use function method_exists; use function preg_match; use function preg_replace_callback; use function strlen; use function strpos; use function strtoupper; use function substr; /** * Walks an expression graph and turns it into a PHP closure. * * This closure can be used with {@Collection#filter()} and is used internally * by {@ArrayCollection#select()}. */ class ClosureExpressionVisitor extends ExpressionVisitor { /** * Accesses the field of a given object. This field has to be public * directly or indirectly (through an accessor get*, is*, or a magic * method, __get, __call). * * @param object|mixed[] $object * @param string $field * * @return mixed */ public static function getObjectFieldValue($object, $field) { if (strpos($field, '.') !== false) { [$field, $subField] = explode('.', $field, 2); $object = self::getObjectFieldValue($object, $field); return self::getObjectFieldValue($object, $subField); } if (is_array($object)) { return $object[$field]; } $accessors = ['get', 'is', '']; foreach ($accessors as $accessor) { $accessor .= $field; if (method_exists($object, $accessor)) { return $object->$accessor(); } } if (preg_match('/^is[A-Z]+/', $field) === 1 && method_exists($object, $field)) { return $object->$field(); } // __call should be triggered for get. $accessor = $accessors[0] . $field; if (method_exists($object, '__call')) { return $object->$accessor(); } if ($object instanceof ArrayAccess) { return $object[$field]; } if (isset($object->$field)) { return $object->$field; } // camelcase field name to support different variable naming conventions $ccField = preg_replace_callback('/_(.?)/', static function ($matches) { return strtoupper($matches[1]); }, $field); foreach ($accessors as $accessor) { $accessor .= $ccField; if (method_exists($object, $accessor)) { return $object->$accessor(); } } return $object->$field; } /** * Helper for sorting arrays of objects based on multiple fields + orientations. * * @param string $name * @param int $orientation * * @return Closure */ public static function sortByField($name, $orientation = 1, ?Closure $next = null) { if (! $next) { $next = static function (): int { return 0; }; } return static function ($a, $b) use ($name, $next, $orientation): int { $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); if ($aValue === $bValue) { return $next($a, $b); } return ($aValue > $bValue ? 1 : -1) * $orientation; }; } /** * {@inheritDoc} */ public function walkComparison(Comparison $comparison) { $field = $comparison->getField(); $value = $comparison->getValue()->getValue(); // shortcut for walkValue() switch ($comparison->getOperator()) { case Comparison::EQ: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; }; case Comparison::NEQ: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; }; case Comparison::LT: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; }; case Comparison::LTE: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; }; case Comparison::GT: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; }; case Comparison::GTE: return static function ($object) use ($field, $value): bool { return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; }; case Comparison::IN: return static function ($object) use ($field, $value): bool { $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); return in_array($fieldValue, $value, is_scalar($fieldValue)); }; case Comparison::NIN: return static function ($object) use ($field, $value): bool { $fieldValue = ClosureExpressionVisitor::getObjectFieldValue($object, $field); return ! in_array($fieldValue, $value, is_scalar($fieldValue)); }; case Comparison::CONTAINS: return static function ($object) use ($field, $value) { return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) !== false; }; case Comparison::MEMBER_OF: return static function ($object) use ($field, $value): bool { $fieldValues = ClosureExpressionVisitor::getObjectFieldValue($object, $field); if (! is_array($fieldValues)) { $fieldValues = iterator_to_array($fieldValues); } return in_array($value, $fieldValues, true); }; case Comparison::STARTS_WITH: return static function ($object) use ($field, $value): bool { return strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value) === 0; }; case Comparison::ENDS_WITH: return static function ($object) use ($field, $value): bool { return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object, $field), -strlen($value)); }; default: throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); } } /** * {@inheritDoc} */ public function walkValue(Value $value) { return $value->getValue(); } /** * {@inheritDoc} */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = []; foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch ($expr->getType()) { case CompositeExpression::TYPE_AND: return $this->andExpressions($expressionList); case CompositeExpression::TYPE_OR: return $this->orExpressions($expressionList); default: throw new RuntimeException('Unknown composite ' . $expr->getType()); } } /** @param callable[] $expressions */ private function andExpressions(array $expressions): callable { return static function ($object) use ($expressions): bool { foreach ($expressions as $expression) { if (! $expression($object)) { return false; } } return true; }; } /** @param callable[] $expressions */ private function orExpressions(array $expressions): callable { return static function ($object) use ($expressions): bool { foreach ($expressions as $expression) { if ($expression($object)) { return true; } } return false; }; } } collections/lib/Doctrine/Common/Collections/Expr/Comparison.php 0000644 00000003145 15120025732 0020646 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; /** * Comparison of a field with a value by the given operator. */ class Comparison implements Expression { public const EQ = '='; public const NEQ = '<>'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; public const IS = '='; // no difference with EQ public const IN = 'IN'; public const NIN = 'NIN'; public const CONTAINS = 'CONTAINS'; public const MEMBER_OF = 'MEMBER_OF'; public const STARTS_WITH = 'STARTS_WITH'; public const ENDS_WITH = 'ENDS_WITH'; /** @var string */ private $field; /** @var string */ private $op; /** @var Value */ private $value; /** * @param string $field * @param string $operator * @param mixed $value */ public function __construct($field, $operator, $value) { if (! ($value instanceof Value)) { $value = new Value($value); } $this->field = $field; $this->op = $operator; $this->value = $value; } /** @return string */ public function getField() { return $this->field; } /** @return Value */ public function getValue() { return $this->value; } /** @return string */ public function getOperator() { return $this->op; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkComparison($this); } } collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php 0000644 00000002707 15120025732 0022561 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; use RuntimeException; /** * Expression of Expressions combined by AND or OR operation. */ class CompositeExpression implements Expression { public const TYPE_AND = 'AND'; public const TYPE_OR = 'OR'; /** @var string */ private $type; /** @var Expression[] */ private $expressions = []; /** * @param string $type * @param mixed[] $expressions * * @throws RuntimeException */ public function __construct($type, array $expressions) { $this->type = $type; foreach ($expressions as $expr) { if ($expr instanceof Value) { throw new RuntimeException('Values are not supported expressions as children of and/or expressions.'); } if (! ($expr instanceof Expression)) { throw new RuntimeException('No expression given to CompositeExpression.'); } $this->expressions[] = $expr; } } /** * Returns the list of expressions nested in this composite. * * @return Expression[] */ public function getExpressionList() { return $this->expressions; } /** @return string */ public function getType() { return $this->type; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkCompositeExpression($this); } } collections/lib/Doctrine/Common/Collections/Expr/Expression.php 0000644 00000000331 15120025732 0020665 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; /** * Expression for the {@link Selectable} interface. */ interface Expression { /** @return mixed */ public function visit(ExpressionVisitor $visitor); } collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php 0000644 00000002724 15120025732 0022255 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; use RuntimeException; use function get_class; /** * An Expression visitor walks a graph of expressions and turns them into a * query for the underlying implementation. */ abstract class ExpressionVisitor { /** * Converts a comparison expression into the target query language output. * * @return mixed */ abstract public function walkComparison(Comparison $comparison); /** * Converts a value expression into the target query language part. * * @return mixed */ abstract public function walkValue(Value $value); /** * Converts a composite expression into the target query language output. * * @return mixed */ abstract public function walkCompositeExpression(CompositeExpression $expr); /** * Dispatches walking an expression to the appropriate handler. * * @return mixed * * @throws RuntimeException */ public function dispatch(Expression $expr) { switch (true) { case $expr instanceof Comparison: return $this->walkComparison($expr); case $expr instanceof Value: return $this->walkValue($expr); case $expr instanceof CompositeExpression: return $this->walkCompositeExpression($expr); default: throw new RuntimeException('Unknown Expression ' . get_class($expr)); } } } collections/lib/Doctrine/Common/Collections/Expr/Value.php 0000644 00000000754 15120025732 0017613 0 ustar 00 <?php namespace Doctrine\Common\Collections\Expr; class Value implements Expression { /** @var mixed */ private $value; /** @param mixed $value */ public function __construct($value) { $this->value = $value; } /** @return mixed */ public function getValue() { return $this->value; } /** * {@inheritDoc} */ public function visit(ExpressionVisitor $visitor) { return $visitor->walkValue($this); } } collections/lib/Doctrine/Common/Collections/AbstractLazyCollection.php 0000644 00000015337 15120025732 0022243 0 ustar 00 <?php namespace Doctrine\Common\Collections; use Closure; use LogicException; use ReturnTypeWillChange; use Traversable; /** * Lazy collection that is backed by a concrete collection * * @psalm-template TKey of array-key * @psalm-template T * @template-implements Collection<TKey,T> */ abstract class AbstractLazyCollection implements Collection { /** * The backed collection to use * * @psalm-var Collection<TKey,T>|null * @var Collection<mixed>|null */ protected $collection; /** @var bool */ protected $initialized = false; /** * {@inheritDoc} * * @return int */ #[ReturnTypeWillChange] public function count() { $this->initialize(); return $this->collection->count(); } /** * {@inheritDoc} */ public function add($element) { $this->initialize(); return $this->collection->add($element); } /** * {@inheritDoc} */ public function clear() { $this->initialize(); $this->collection->clear(); } /** * {@inheritDoc} * * @template TMaybeContained */ public function contains($element) { $this->initialize(); return $this->collection->contains($element); } /** * {@inheritDoc} */ public function isEmpty() { $this->initialize(); return $this->collection->isEmpty(); } /** * {@inheritDoc} */ public function remove($key) { $this->initialize(); return $this->collection->remove($key); } /** * {@inheritDoc} */ public function removeElement($element) { $this->initialize(); return $this->collection->removeElement($element); } /** * {@inheritDoc} */ public function containsKey($key) { $this->initialize(); return $this->collection->containsKey($key); } /** * {@inheritDoc} */ public function get($key) { $this->initialize(); return $this->collection->get($key); } /** * {@inheritDoc} */ public function getKeys() { $this->initialize(); return $this->collection->getKeys(); } /** * {@inheritDoc} */ public function getValues() { $this->initialize(); return $this->collection->getValues(); } /** * {@inheritDoc} */ public function set($key, $value) { $this->initialize(); $this->collection->set($key, $value); } /** * {@inheritDoc} */ public function toArray() { $this->initialize(); return $this->collection->toArray(); } /** * {@inheritDoc} */ public function first() { $this->initialize(); return $this->collection->first(); } /** * {@inheritDoc} */ public function last() { $this->initialize(); return $this->collection->last(); } /** * {@inheritDoc} */ public function key() { $this->initialize(); return $this->collection->key(); } /** * {@inheritDoc} */ public function current() { $this->initialize(); return $this->collection->current(); } /** * {@inheritDoc} */ public function next() { $this->initialize(); return $this->collection->next(); } /** * {@inheritDoc} */ public function exists(Closure $p) { $this->initialize(); return $this->collection->exists($p); } /** * {@inheritDoc} */ public function filter(Closure $p) { $this->initialize(); return $this->collection->filter($p); } /** * {@inheritDoc} */ public function forAll(Closure $p) { $this->initialize(); return $this->collection->forAll($p); } /** * {@inheritDoc} */ public function map(Closure $func) { $this->initialize(); return $this->collection->map($func); } /** * {@inheritDoc} */ public function partition(Closure $p) { $this->initialize(); return $this->collection->partition($p); } /** * {@inheritDoc} * * @template TMaybeContained */ public function indexOf($element) { $this->initialize(); return $this->collection->indexOf($element); } /** * {@inheritDoc} */ public function slice($offset, $length = null) { $this->initialize(); return $this->collection->slice($offset, $length); } /** * {@inheritDoc} * * @return Traversable<int|string, mixed> * @psalm-return Traversable<TKey,T> */ #[ReturnTypeWillChange] public function getIterator() { $this->initialize(); return $this->collection->getIterator(); } /** * @param TKey $offset * * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { $this->initialize(); return $this->collection->offsetExists($offset); } /** * @param TKey $offset * * @return mixed */ #[ReturnTypeWillChange] public function offsetGet($offset) { $this->initialize(); return $this->collection->offsetGet($offset); } /** * @param TKey|null $offset * @param T $value * * @return void */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->initialize(); $this->collection->offsetSet($offset, $value); } /** * @param TKey $offset * * @return void */ #[ReturnTypeWillChange] public function offsetUnset($offset) { $this->initialize(); $this->collection->offsetUnset($offset); } /** * Is the lazy collection already initialized? * * @return bool * * @psalm-assert-if-true Collection<TKey,T> $this->collection */ public function isInitialized() { return $this->initialized; } /** * Initialize the collection * * @return void * * @psalm-assert Collection<TKey,T> $this->collection */ protected function initialize() { if ($this->initialized) { return; } $this->doInitialize(); $this->initialized = true; if ($this->collection === null) { throw new LogicException('You must initialize the collection property in the doInitialize() method.'); } } /** * Do the initialization logic * * @return void */ abstract protected function doInitialize(); } collections/lib/Doctrine/Common/Collections/ArrayCollection.php 0000644 00000022602 15120025732 0020707 0 ustar 00 <?php namespace Doctrine\Common\Collections; use ArrayIterator; use Closure; use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor; use ReturnTypeWillChange; use Traversable; use function array_filter; use function array_key_exists; use function array_keys; use function array_map; use function array_reverse; use function array_search; use function array_slice; use function array_values; use function count; use function current; use function end; use function in_array; use function key; use function next; use function reset; use function spl_object_hash; use function uasort; use const ARRAY_FILTER_USE_BOTH; /** * An ArrayCollection is a Collection implementation that wraps a regular PHP array. * * Warning: Using (un-)serialize() on a collection is not a supported use-case * and may break when we change the internals in the future. If you need to * serialize a collection use {@link toArray()} and reconstruct the collection * manually. * * @psalm-template TKey of array-key * @psalm-template T * @template-implements Collection<TKey,T> * @template-implements Selectable<TKey,T> * @psalm-consistent-constructor */ class ArrayCollection implements Collection, Selectable { /** * An array containing the entries of this collection. * * @psalm-var array<TKey,T> * @var mixed[] */ private $elements; /** * Initializes a new ArrayCollection. * * @param array $elements * @psalm-param array<TKey,T> $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } /** * {@inheritDoc} */ public function toArray() { return $this->elements; } /** * {@inheritDoc} */ public function first() { return reset($this->elements); } /** * Creates a new instance from the specified elements. * * This method is provided for derived classes to specify how a new * instance should be created when constructor semantics have changed. * * @param array $elements Elements. * @psalm-param array<K,V> $elements * * @return static * @psalm-return static<K,V> * * @psalm-template K of array-key * @psalm-template V */ protected function createFrom(array $elements) { return new static($elements); } /** * {@inheritDoc} */ public function last() { return end($this->elements); } /** * {@inheritDoc} */ public function key() { return key($this->elements); } /** * {@inheritDoc} */ public function next() { return next($this->elements); } /** * {@inheritDoc} */ public function current() { return current($this->elements); } /** * {@inheritDoc} */ public function remove($key) { if (! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) { return null; } $removed = $this->elements[$key]; unset($this->elements[$key]); return $removed; } /** * {@inheritDoc} */ public function removeElement($element) { $key = array_search($element, $this->elements, true); if ($key === false) { return false; } unset($this->elements[$key]); return true; } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { return $this->containsKey($offset); } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return mixed */ #[ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); } /** * Required by interface ArrayAccess. * * @param TKey|null $offset * @param T $value * * @return void */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if (! isset($offset)) { $this->add($value); return; } $this->set($offset, $value); } /** * Required by interface ArrayAccess. * * @param TKey $offset * * @return void */ #[ReturnTypeWillChange] public function offsetUnset($offset) { $this->remove($offset); } /** * {@inheritDoc} */ public function containsKey($key) { return isset($this->elements[$key]) || array_key_exists($key, $this->elements); } /** * {@inheritDoc} * * @template TMaybeContained */ public function contains($element) { return in_array($element, $this->elements, true); } /** * {@inheritDoc} */ public function exists(Closure $p) { foreach ($this->elements as $key => $element) { if ($p($key, $element)) { return true; } } return false; } /** * {@inheritDoc} * * @psalm-param TMaybeContained $element * * @psalm-return (TMaybeContained is T ? TKey|false : false) * * @template TMaybeContained */ public function indexOf($element) { return array_search($element, $this->elements, true); } /** * {@inheritDoc} */ public function get($key) { return $this->elements[$key] ?? null; } /** * {@inheritDoc} */ public function getKeys() { return array_keys($this->elements); } /** * {@inheritDoc} */ public function getValues() { return array_values($this->elements); } /** * {@inheritDoc} * * @return int */ #[ReturnTypeWillChange] public function count() { return count($this->elements); } /** * {@inheritDoc} */ public function set($key, $value) { $this->elements[$key] = $value; } /** * {@inheritDoc} * * @psalm-suppress InvalidPropertyAssignmentValue * * This breaks assumptions about the template type, but it would * be a backwards-incompatible change to remove this method */ public function add($element) { $this->elements[] = $element; return true; } /** * {@inheritDoc} */ public function isEmpty() { return empty($this->elements); } /** * {@inheritDoc} * * @return Traversable<int|string, mixed> * @psalm-return Traversable<TKey,T> */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->elements); } /** * {@inheritDoc} * * @psalm-param Closure(T):U $func * * @return static * @psalm-return static<TKey, U> * * @psalm-template U */ public function map(Closure $func) { return $this->createFrom(array_map($func, $this->elements)); } /** * {@inheritDoc} * * @return static * @psalm-return static<TKey,T> */ public function filter(Closure $p) { return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH)); } /** * {@inheritDoc} */ public function forAll(Closure $p) { foreach ($this->elements as $key => $element) { if (! $p($key, $element)) { return false; } } return true; } /** * {@inheritDoc} */ public function partition(Closure $p) { $matches = $noMatches = []; foreach ($this->elements as $key => $element) { if ($p($key, $element)) { $matches[$key] = $element; } else { $noMatches[$key] = $element; } } return [$this->createFrom($matches), $this->createFrom($noMatches)]; } /** * Returns a string representation of this object. * * @return string */ public function __toString() { return self::class . '@' . spl_object_hash($this); } /** * {@inheritDoc} */ public function clear() { $this->elements = []; } /** * {@inheritDoc} */ public function slice($offset, $length = null) { return array_slice($this->elements, $offset, $length, true); } /** * {@inheritDoc} */ public function matching(Criteria $criteria) { $expr = $criteria->getWhereExpression(); $filtered = $this->elements; if ($expr) { $visitor = new ClosureExpressionVisitor(); $filter = $visitor->dispatch($expr); $filtered = array_filter($filtered, $filter); } $orderings = $criteria->getOrderings(); if ($orderings) { $next = null; foreach (array_reverse($orderings) as $field => $ordering) { $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next); } uasort($filtered, $next); } $offset = $criteria->getFirstResult(); $length = $criteria->getMaxResults(); if ($offset || $length) { $filtered = array_slice($filtered, (int) $offset, $length); } return $this->createFrom($filtered); } } collections/lib/Doctrine/Common/Collections/Collection.php 0000644 00000006175 15120025732 0017717 0 ustar 00 <?php namespace Doctrine\Common\Collections; use ArrayAccess; use Closure; /** * The missing (SPL) Collection/Array/OrderedMap interface. * * A Collection resembles the nature of a regular PHP array. That is, * it is essentially an <b>ordered map</b> that can also be used * like a list. * * A Collection has an internal iterator just like a PHP array. In addition, * a Collection can be iterated with external iterators, which is preferable. * To use an external iterator simply use the foreach language construct to * iterate over the collection (which calls {@link getIterator()} internally) or * explicitly retrieve an iterator though {@link getIterator()} which can then be * used to iterate over the collection. * You can not rely on the internal iterator of the collection being at a certain * position unless you explicitly positioned it before. Prefer iteration with * external iterators. * * @psalm-template TKey of array-key * @psalm-template T * @template-extends ReadableCollection<TKey, T> * @template-extends ArrayAccess<TKey, T> */ interface Collection extends ReadableCollection, ArrayAccess { /** * Adds an element at the end of the collection. * * @param mixed $element The element to add. * @psalm-param T $element * * @return true Always TRUE. */ public function add($element); /** * Clears the collection, removing all elements. * * @return void */ public function clear(); /** * Removes the element at the specified index from the collection. * * @param string|int $key The key/index of the element to remove. * @psalm-param TKey $key * * @return mixed The removed element or NULL, if the collection did not contain the element. * @psalm-return T|null */ public function remove($key); /** * Removes the specified element from the collection, if it is found. * * @param mixed $element The element to remove. * @psalm-param T $element * * @return bool TRUE if this collection contained the specified element, FALSE otherwise. */ public function removeElement($element); /** * Sets an element in the collection at the specified key/index. * * @param string|int $key The key/index of the element to set. * @param mixed $value The element to set. * @psalm-param TKey $key * @psalm-param T $value * * @return void */ public function set($key, $value); /** * {@inheritdoc} * * @return Collection<mixed> A collection with the results of the filter operation. * @psalm-return Collection<TKey, T> */ public function filter(Closure $p); /** * {@inheritdoc} * * @return Collection<mixed>[] An array with two elements. The first element contains the collection * of elements where the predicate returned TRUE, the second element * contains the collection of elements where the predicate returned FALSE. * @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>} */ public function partition(Closure $p); } collections/lib/Doctrine/Common/Collections/Criteria.php 0000644 00000013002 15120025732 0017351 0 ustar 00 <?php namespace Doctrine\Common\Collections; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\Expression; use Doctrine\Deprecations\Deprecation; use function array_map; use function func_num_args; use function strtoupper; /** * Criteria for filtering Selectable collections. * * @psalm-consistent-constructor */ class Criteria { public const ASC = 'ASC'; public const DESC = 'DESC'; /** @var ExpressionBuilder|null */ private static $expressionBuilder; /** @var Expression|null */ private $expression; /** @var string[] */ private $orderings = []; /** @var int|null */ private $firstResult; /** @var int|null */ private $maxResults; /** * Creates an instance of the class. * * @return Criteria */ public static function create() { return new static(); } /** * Returns the expression builder. * * @return ExpressionBuilder */ public static function expr() { if (self::$expressionBuilder === null) { self::$expressionBuilder = new ExpressionBuilder(); } return self::$expressionBuilder; } /** * Construct a new Criteria. * * @param string[]|null $orderings * @param int|null $firstResult * @param int|null $maxResults */ public function __construct(?Expression $expression = null, ?array $orderings = null, $firstResult = null, $maxResults = null) { $this->expression = $expression; if ($firstResult === null && func_num_args() > 2) { Deprecation::trigger( 'doctrine/collections', 'https://github.com/doctrine/collections/pull/311', 'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.', self::class ); } $this->setFirstResult($firstResult); $this->setMaxResults($maxResults); if ($orderings === null) { return; } $this->orderBy($orderings); } /** * Sets the where expression to evaluate when this Criteria is searched for. * * @return $this */ public function where(Expression $expression) { $this->expression = $expression; return $this; } /** * Appends the where expression to evaluate when this Criteria is searched for * using an AND with previous expression. * * @return $this */ public function andWhere(Expression $expression) { if ($this->expression === null) { return $this->where($expression); } $this->expression = new CompositeExpression( CompositeExpression::TYPE_AND, [$this->expression, $expression] ); return $this; } /** * Appends the where expression to evaluate when this Criteria is searched for * using an OR with previous expression. * * @return $this */ public function orWhere(Expression $expression) { if ($this->expression === null) { return $this->where($expression); } $this->expression = new CompositeExpression( CompositeExpression::TYPE_OR, [$this->expression, $expression] ); return $this; } /** * Gets the expression attached to this Criteria. * * @return Expression|null */ public function getWhereExpression() { return $this->expression; } /** * Gets the current orderings of this Criteria. * * @return string[] */ public function getOrderings() { return $this->orderings; } /** * Sets the ordering of the result of this Criteria. * * Keys are field and values are the order, being either ASC or DESC. * * @see Criteria::ASC * @see Criteria::DESC * * @param string[] $orderings * * @return $this */ public function orderBy(array $orderings) { $this->orderings = array_map( static function (string $ordering): string { return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC; }, $orderings ); return $this; } /** * Gets the current first result option of this Criteria. * * @return int|null */ public function getFirstResult() { return $this->firstResult; } /** * Set the number of first result that this Criteria should return. * * @param int|null $firstResult The value to set. * * @return $this */ public function setFirstResult($firstResult) { if ($firstResult === null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/collections', 'https://github.com/doctrine/collections/pull/311', 'Passing null to %s() is deprecated, pass 0 instead.', __METHOD__ ); } $this->firstResult = $firstResult; return $this; } /** * Gets maxResults. * * @return int|null */ public function getMaxResults() { return $this->maxResults; } /** * Sets maxResults. * * @param int|null $maxResults The value to set. * * @return $this */ public function setMaxResults($maxResults) { $this->maxResults = $maxResults; return $this; } } collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php 0000644 00000007701 15120025732 0021266 0 ustar 00 <?php namespace Doctrine\Common\Collections; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\Value; use function func_get_args; /** * Builder for Expressions in the {@link Selectable} interface. * * Important Notice for interoperable code: You have to use scalar * values only for comparisons, otherwise the behavior of the comparison * may be different between implementations (Array vs ORM vs ODM). */ class ExpressionBuilder { /** * @param mixed ...$x * * @return CompositeExpression */ public function andX($x = null) { return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); } /** * @param mixed ...$x * * @return CompositeExpression */ public function orX($x = null) { return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function eq($field, $value) { return new Comparison($field, Comparison::EQ, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function gt($field, $value) { return new Comparison($field, Comparison::GT, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function lt($field, $value) { return new Comparison($field, Comparison::LT, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function gte($field, $value) { return new Comparison($field, Comparison::GTE, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function lte($field, $value) { return new Comparison($field, Comparison::LTE, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function neq($field, $value) { return new Comparison($field, Comparison::NEQ, new Value($value)); } /** * @param string $field * * @return Comparison */ public function isNull($field) { return new Comparison($field, Comparison::EQ, new Value(null)); } /** * @param string $field * @param mixed[] $values * * @return Comparison */ public function in($field, array $values) { return new Comparison($field, Comparison::IN, new Value($values)); } /** * @param string $field * @param mixed[] $values * * @return Comparison */ public function notIn($field, array $values) { return new Comparison($field, Comparison::NIN, new Value($values)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function contains($field, $value) { return new Comparison($field, Comparison::CONTAINS, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function memberOf($field, $value) { return new Comparison($field, Comparison::MEMBER_OF, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function startsWith($field, $value) { return new Comparison($field, Comparison::STARTS_WITH, new Value($value)); } /** * @param string $field * @param mixed $value * * @return Comparison */ public function endsWith($field, $value) { return new Comparison($field, Comparison::ENDS_WITH, new Value($value)); } } collections/lib/Doctrine/Common/Collections/ReadableCollection.php 0000644 00000015122 15120025732 0021327 0 ustar 00 <?php namespace Doctrine\Common\Collections; use Closure; use Countable; use IteratorAggregate; /** * @psalm-template TKey of array-key * @template-covariant T * @template-extends IteratorAggregate<TKey, T> */ interface ReadableCollection extends Countable, IteratorAggregate { /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @param mixed $element The element to search for. * @psalm-param TMaybeContained $element * * @return bool TRUE if the collection contains the element, FALSE otherwise. * @psalm-return (TMaybeContained is T ? bool : false) * * @template TMaybeContained */ public function contains($element); /** * Checks whether the collection is empty (contains no elements). * * @return bool TRUE if the collection is empty, FALSE otherwise. */ public function isEmpty(); /** * Checks whether the collection contains an element with the specified key/index. * * @param string|int $key The key/index to check for. * @psalm-param TKey $key * * @return bool TRUE if the collection contains an element with the specified key/index, * FALSE otherwise. */ public function containsKey($key); /** * Gets the element at the specified key/index. * * @param string|int $key The key/index of the element to retrieve. * @psalm-param TKey $key * * @return mixed * @psalm-return T|null */ public function get($key); /** * Gets all keys/indices of the collection. * * @return int[]|string[] The keys/indices of the collection, in the order of the corresponding * elements in the collection. * @psalm-return list<TKey> */ public function getKeys(); /** * Gets all values of the collection. * * @return mixed[] The values of all elements in the collection, in the * order they appear in the collection. * @psalm-return list<T> */ public function getValues(); /** * Gets a native PHP array representation of the collection. * * @return mixed[] * @psalm-return array<TKey,T> */ public function toArray(); /** * Sets the internal iterator to the first element in the collection and returns this element. * * @return mixed * @psalm-return T|false */ public function first(); /** * Sets the internal iterator to the last element in the collection and returns this element. * * @return mixed * @psalm-return T|false */ public function last(); /** * Gets the key/index of the element at the current iterator position. * * @return int|string|null * @psalm-return TKey|null */ public function key(); /** * Gets the element of the collection at the current iterator position. * * @return mixed * @psalm-return T|false */ public function current(); /** * Moves the internal iterator position to the next element and returns this element. * * @return mixed * @psalm-return T|false */ public function next(); /** * Extracts a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset The offset to start from. * @param int|null $length The maximum number of elements to return, or null for no limit. * * @return mixed[] * @psalm-return array<TKey,T> */ public function slice($offset, $length = null); /** * Tests for the existence of an element that satisfies the given predicate. * * @param Closure $p The predicate. * @psalm-param Closure(TKey, T):bool $p * * @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise. */ public function exists(Closure $p); /** * Returns all the elements of this collection that satisfy the predicate p. * The order of the elements is preserved. * * @param Closure $p The predicate used for filtering. * @psalm-param Closure(T):bool $p * * @return ReadableCollection<mixed> A collection with the results of the filter operation. * @psalm-return ReadableCollection<TKey, T> */ public function filter(Closure $p); /** * Applies the given function to each element in the collection and returns * a new collection with the elements returned by the function. * * @psalm-param Closure(T):U $func * * @return Collection<mixed> * @psalm-return Collection<TKey, U> * * @psalm-template U */ public function map(Closure $func); /** * Partitions this collection in two collections according to a predicate. * Keys are preserved in the resulting collections. * * @param Closure $p The predicate on which to partition. * @psalm-param Closure(TKey, T):bool $p * * @return ReadableCollection<mixed>[] An array with two elements. The first element contains the collection * of elements where the predicate returned TRUE, the second element * contains the collection of elements where the predicate returned FALSE. * @psalm-return array{0: ReadableCollection<TKey, T>, 1: ReadableCollection<TKey, T>} */ public function partition(Closure $p); /** * Tests whether the given predicate p holds for all elements of this collection. * * @param Closure $p The predicate. * @psalm-param Closure(TKey, T):bool $p * * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. */ public function forAll(Closure $p); /** * Gets the index/key of a given element. The comparison of two elements is strict, * that means not only the value but also the type must match. * For objects this means reference equality. * * @param mixed $element The element to search for. * @psalm-param TMaybeContained $element * * @return int|string|bool The key/index of the element or FALSE if the element was not found. * @psalm-return (TMaybeContained is T ? TKey|false : false) * * @template TMaybeContained */ public function indexOf($element); } collections/lib/Doctrine/Common/Collections/Selectable.php 0000644 00000002012 15120025732 0017651 0 ustar 00 <?php namespace Doctrine\Common\Collections; /** * Interface for collections that allow efficient filtering with an expression API. * * Goal of this interface is a backend independent method to fetch elements * from a collections. {@link Expression} is crafted in a way that you can * implement queries from both in-memory and database-backed collections. * * For database backed collections this allows very efficient access by * utilizing the query APIs, for example SQL in the ORM. Applications using * this API can implement efficient database access without having to ask the * EntityManager or Repositories. * * @psalm-template TKey as array-key * @psalm-template T */ interface Selectable { /** * Selects all elements from a selectable that match the expression and * returns a new collection containing these elements. * * @return Collection<mixed>&Selectable<mixed> * @psalm-return Collection<TKey,T>&Selectable<TKey,T> */ public function matching(Criteria $criteria); } collections/.doctrine-project.json 0000644 00000001276 15120025732 0013311 0 ustar 00 { "active": true, "name": "Collections", "slug": "collections", "docsSlug": "doctrine-collections", "versions": [ { "name": "2.0", "branchName": "2.0.x", "slug": "latest", "upcoming": true }, { "name": "1.8", "branchName": "1.8.x", "slug": "1.8", "upcoming": true }, { "name": "1.7", "branchName": "1.7.x", "slug": "1.7", "current": true }, { "name": "1.6", "branchName": "1.6.x", "slug": "1.6", "maintained": false } ] } collections/CONTRIBUTING.md 0000644 00000002641 15120025732 0011313 0 ustar 00 # Contribute to Doctrine Thank you for contributing to Doctrine! Before we can merge your Pull-Request here are some guidelines that you need to follow. These guidelines exist not to annoy you, but to keep the code base clean, unified and future proof. ## Coding Standard We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). ## Unit-Tests Please try to add a test for your pull-request. * If you want to contribute new functionality add unit- or functional tests depending on the scope of the feature. You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project. It will run all the project tests. In order to do that, you will need a fresh copy of doctrine/collections, and you will have to run a composer installation in the project: ```sh git clone git@github.com:doctrine/collections.git cd collections curl -sS https://getcomposer.org/installer | php -- ./composer.phar install ``` ## Github Actions We automatically run your pull request through Github Actions against supported PHP versions. If you break the tests, we cannot merge your code, so please make sure that your code is working before opening up a Pull-Request. ## Getting merged Please allow us time to review your pull requests. We will give our best to review everything as fast as possible, but cannot always live up to our own expectations. Thank you very much again for your contribution! collections/LICENSE 0000644 00000002051 15120025732 0010062 0 ustar 00 Copyright (c) 2006-2013 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. collections/README.md 0000644 00000000547 15120025732 0010344 0 ustar 00 # Doctrine Collections [](https://github.com/doctrine/collections/actions) [](https://codecov.io/gh/doctrine/collections/branch/2.0.x) Collections Abstraction library collections/composer.json 0000644 00000003175 15120025732 0011607 0 ustar 00 { "name": "doctrine/collections", "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", "license": "MIT", "type": "library", "keywords": [ "php", "collections", "array", "iterators" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/collections.html", "require": { "php": "^7.1.3 || ^8.0", "doctrine/deprecations": "^0.5.3 || ^1" }, "require-dev": { "doctrine/coding-standard": "^9.0 || ^10.0", "phpstan/phpstan": "^1.4.8", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", "vimeo/psalm": "^4.22" }, "autoload": { "psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true } } } event-manager/src/EventArgs.php 0000644 00000002452 15120025732 0012473 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common; /** * EventArgs is the base class for classes containing event data. * * This class contains no event data. It is used by events that do not pass state * information to an event handler when an event is raised. The single empty EventArgs * instance can be obtained through {@link getEmptyInstance}. */ class EventArgs { /** * Single instance of EventArgs. * * @var EventArgs|null */ private static $_emptyEventArgsInstance; /** * Gets the single, empty and immutable EventArgs instance. * * This instance will be used when events are dispatched without any parameter, * like this: EventManager::dispatchEvent('eventname'); * * The benefit from this is that only one empty instance is instantiated and shared * (otherwise there would be instances for every dispatched in the abovementioned form). * * @link https://msdn.microsoft.com/en-us/library/system.eventargs.aspx * @see EventManager::dispatchEvent * * @return EventArgs */ public static function getEmptyInstance() { if (! self::$_emptyEventArgsInstance) { self::$_emptyEventArgsInstance = new EventArgs(); } return self::$_emptyEventArgsInstance; } } event-manager/src/EventManager.php 0000644 00000011135 15120025732 0013147 0 ustar 00 <?php namespace Doctrine\Common; use Doctrine\Deprecations\Deprecation; use function spl_object_hash; /** * The EventManager is the central point of Doctrine's event listener system. * Listeners are registered on the manager and events are dispatched through the * manager. */ class EventManager { /** * Map of registered listeners. * <event> => <listeners> * * @var array<string, object[]> */ private $listeners = []; /** * Dispatches an event to all registered listeners. * * @param string $eventName The name of the event to dispatch. The name of the event is * the name of the method that is invoked on listeners. * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. * If not supplied, the single empty EventArgs instance is used. * * @return void */ public function dispatchEvent($eventName, ?EventArgs $eventArgs = null) { if (! isset($this->listeners[$eventName])) { return; } $eventArgs = $eventArgs ?? EventArgs::getEmptyInstance(); foreach ($this->listeners[$eventName] as $listener) { $listener->$eventName($eventArgs); } } /** * Gets the listeners of a specific event. * * @param string|null $event The name of the event. * * @return object[]|array<string, object[]> The event listeners for the specified event, or all event listeners. * @psalm-return ($event is null ? array<string, object[]> : object[]) */ public function getListeners($event = null) { if ($event === null) { Deprecation::trigger( 'doctrine/event-manager', 'https://github.com/doctrine/event-manager/pull/50', 'Calling %s without an event name is deprecated. Call getAllListeners() instead.', __METHOD__ ); return $this->getAllListeners(); } return $this->listeners[$event] ?? []; } /** * Gets all listeners keyed by event name. * * @return array<string, object[]> The event listeners for the specified event, or all event listeners. */ public function getAllListeners(): array { return $this->listeners; } /** * Checks whether an event has any registered listeners. * * @param string $event * * @return bool TRUE if the specified event has any listeners, FALSE otherwise. */ public function hasListeners($event) { return ! empty($this->listeners[$event]); } /** * Adds an event listener that listens on the specified events. * * @param string|string[] $events The event(s) to listen on. * @param object $listener The listener object. * * @return void */ public function addEventListener($events, $listener) { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) $this->listeners[$event][$hash] = $listener; } } /** * Removes an event listener from the specified events. * * @param string|string[] $events * @param object $listener * * @return void */ public function removeEventListener($events, $listener) { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { unset($this->listeners[$event][$hash]); } } /** * Adds an EventSubscriber. The subscriber is asked for all the events it is * interested in and added as a listener for these events. * * @param EventSubscriber $subscriber The subscriber. * * @return void */ public function addEventSubscriber(EventSubscriber $subscriber) { $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); } /** * Removes an EventSubscriber. The subscriber is asked for all the events it is * interested in and removed as a listener for these events. * * @param EventSubscriber $subscriber The subscriber. * * @return void */ public function removeEventSubscriber(EventSubscriber $subscriber) { $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); } } event-manager/src/EventSubscriber.php 0000644 00000000760 15120025732 0013702 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common; /** * An EventSubscriber knows what events it is interested in. * If an EventSubscriber is added to an EventManager, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. */ interface EventSubscriber { /** * Returns an array of events this subscriber wants to listen to. * * @return string[] */ public function getSubscribedEvents(); } event-manager/LICENSE 0000644 00000002051 15120025732 0010275 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. event-manager/README.md 0000644 00000001510 15120025732 0010546 0 ustar 00 # Doctrine Event Manager [](https://github.com/doctrine/event-manager/actions) [](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) [](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x) The Doctrine Event Manager is a library that provides a simple event system. ## More resources: * [Website](https://www.doctrine-project.org/) * [Documentation](https://www.doctrine-project.org/projects/doctrine-event-manager/en/latest/) * [Downloads](https://github.com/doctrine/event-manager/releases) event-manager/UPGRADE.md 0000644 00000000475 15120025732 0010711 0 ustar 00 # Upgrade to 1.2 ## Deprecated calling `EventManager::getListeners()` without an event name When calling `EventManager::getListeners()` without an event name, all listeners were returned, keyed by event name. A new method `getAllListeners()` has been added to provide this functionality. It should be used instead. event-manager/composer.json 0000644 00000003430 15120025732 0012014 0 ustar 00 { "name": "doctrine/event-manager", "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", "license": "MIT", "type": "library", "keywords": [ "events", "event", "event dispatcher", "event manager", "event system" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" }, { "name": "Marco Pivetta", "email": "ocramius@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "require": { "php": "^7.1 || ^8.0", "doctrine/deprecations": "^0.5.3 || ^1" }, "require-dev": { "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "~1.4.10 || ^1.8.8", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "vimeo/psalm": "^4.24" }, "conflict": { "doctrine/common": "<2.9" }, "autoload": { "psr-4": { "Doctrine\\Common\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\Common\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true } } event-manager/phpstan.neon.dist 0000644 00000000070 15120025732 0012567 0 ustar 00 parameters: level: 9 paths: - src/ - tests/ event-manager/psalm.xml 0000644 00000000674 15120025732 0011137 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="2" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> persistence/src/Persistence/Event/LifecycleEventArgs.php 0000644 00000002136 15120025732 0017452 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Event; use Doctrine\Common\EventArgs; use Doctrine\Persistence\ObjectManager; /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * * @template-covariant TObjectManager of ObjectManager */ class LifecycleEventArgs extends EventArgs { /** * @var ObjectManager * @psalm-var TObjectManager */ private $objectManager; /** @var object */ private $object; /** @psalm-param TObjectManager $objectManager */ public function __construct(object $object, ObjectManager $objectManager) { $this->object = $object; $this->objectManager = $objectManager; } /** * Retrieves the associated object. * * @return object */ public function getObject() { return $this->object; } /** * Retrieves the associated ObjectManager. * * @return ObjectManager * @psalm-return TObjectManager */ public function getObjectManager() { return $this->objectManager; } } persistence/src/Persistence/Event/LoadClassMetadataEventArgs.php 0000644 00000002535 15120025732 0021064 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Event; use Doctrine\Common\EventArgs; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; /** * Class that holds event arguments for a loadMetadata event. * * @template-covariant TClassMetadata of ClassMetadata<object> * @template-covariant TObjectManager of ObjectManager */ class LoadClassMetadataEventArgs extends EventArgs { /** * @var ClassMetadata * @psalm-var TClassMetadata */ private $classMetadata; /** * @var ObjectManager * @psalm-var TObjectManager */ private $objectManager; /** * @psalm-param TClassMetadata $classMetadata * @psalm-param TObjectManager $objectManager */ public function __construct(ClassMetadata $classMetadata, ObjectManager $objectManager) { $this->classMetadata = $classMetadata; $this->objectManager = $objectManager; } /** * Retrieves the associated ClassMetadata. * * @return ClassMetadata * @psalm-return TClassMetadata */ public function getClassMetadata() { return $this->classMetadata; } /** * Retrieves the associated ObjectManager. * * @return TObjectManager */ public function getObjectManager() { return $this->objectManager; } } persistence/src/Persistence/Event/ManagerEventArgs.php 0000644 00000001452 15120025732 0017125 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Event; use Doctrine\Common\EventArgs; use Doctrine\Persistence\ObjectManager; /** * Provides event arguments for the preFlush event. * * @template-covariant TObjectManager of ObjectManager */ class ManagerEventArgs extends EventArgs { /** * @var ObjectManager * @psalm-var TObjectManager */ private $objectManager; /** @psalm-param TObjectManager $objectManager */ public function __construct(ObjectManager $objectManager) { $this->objectManager = $objectManager; } /** * Retrieves the associated ObjectManager. * * @return ObjectManager * @psalm-return TObjectManager */ public function getObjectManager() { return $this->objectManager; } } persistence/src/Persistence/Event/OnClearEventArgs.php 0000644 00000001564 15120025732 0017102 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Event; use Doctrine\Common\EventArgs; use Doctrine\Persistence\ObjectManager; /** * Provides event arguments for the onClear event. * * @template-covariant TObjectManager of ObjectManager */ class OnClearEventArgs extends EventArgs { /** * @var ObjectManager * @psalm-var TObjectManager */ private $objectManager; /** * @param ObjectManager $objectManager The object manager. * @psalm-param TObjectManager $objectManager */ public function __construct(ObjectManager $objectManager) { $this->objectManager = $objectManager; } /** * Retrieves the associated ObjectManager. * * @return ObjectManager * @psalm-return TObjectManager */ public function getObjectManager() { return $this->objectManager; } } persistence/src/Persistence/Event/PreUpdateEventArgs.php 0000644 00000004754 15120025732 0017454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Event; use Doctrine\Persistence\ObjectManager; use InvalidArgumentException; use function get_class; use function sprintf; /** * Class that holds event arguments for a preUpdate event. * * @template-covariant TObjectManager of ObjectManager * @extends LifecycleEventArgs<TObjectManager> */ class PreUpdateEventArgs extends LifecycleEventArgs { /** @var array<string, array<int, mixed>> */ private $entityChangeSet; /** * @param array<string, array<int, mixed>> $changeSet * @psalm-param TObjectManager $objectManager */ public function __construct(object $entity, ObjectManager $objectManager, array &$changeSet) { parent::__construct($entity, $objectManager); $this->entityChangeSet = &$changeSet; } /** * Retrieves the entity changeset. * * @return array<string, array<int, mixed>> */ public function getEntityChangeSet() { return $this->entityChangeSet; } /** * Checks if field has a changeset. * * @return bool */ public function hasChangedField(string $field) { return isset($this->entityChangeSet[$field]); } /** * Gets the old value of the changeset of the changed field. * * @return mixed */ public function getOldValue(string $field) { $this->assertValidField($field); return $this->entityChangeSet[$field][0]; } /** * Gets the new value of the changeset of the changed field. * * @return mixed */ public function getNewValue(string $field) { $this->assertValidField($field); return $this->entityChangeSet[$field][1]; } /** * Sets the new value of this field. * * @param mixed $value * * @return void */ public function setNewValue(string $field, $value) { $this->assertValidField($field); $this->entityChangeSet[$field][1] = $value; } /** * Asserts the field exists in changeset. * * @return void * * @throws InvalidArgumentException */ private function assertValidField(string $field) { if (! isset($this->entityChangeSet[$field])) { throw new InvalidArgumentException(sprintf( 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', $field, get_class($this->getObject()) )); } } } persistence/src/Persistence/Mapping/Driver/ColocatedMappingDriver.php 0000644 00000012362 15120025732 0022070 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\MappingException; use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RecursiveRegexIterator; use ReflectionClass; use RegexIterator; use function array_merge; use function array_unique; use function assert; use function get_declared_classes; use function in_array; use function is_dir; use function preg_match; use function preg_quote; use function realpath; use function str_replace; use function strpos; /** * The ColocatedMappingDriver reads the mapping metadata located near the code. */ trait ColocatedMappingDriver { /** * The paths where to look for mapping files. * * @var array<int, string> */ protected $paths = []; /** * The paths excluded from path where to look for mapping files. * * @var array<int, string> */ protected $excludePaths = []; /** * The file extension of mapping documents. * * @var string */ protected $fileExtension = '.php'; /** * Cache for getAllClassNames(). * * @var array<int, string>|null * @psalm-var list<class-string>|null */ protected $classNames; /** * Appends lookup paths to metadata driver. * * @param array<int, string> $paths * * @return void */ public function addPaths(array $paths) { $this->paths = array_unique(array_merge($this->paths, $paths)); } /** * Retrieves the defined metadata lookup paths. * * @return array<int, string> */ public function getPaths() { return $this->paths; } /** * Append exclude lookup paths to metadata driver. * * @param string[] $paths * * @return void */ public function addExcludePaths(array $paths) { $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); } /** * Retrieve the defined metadata lookup exclude paths. * * @return array<int, string> */ public function getExcludePaths() { return $this->excludePaths; } /** * Gets the file extension used to look for mapping files under. * * @return string */ public function getFileExtension() { return $this->fileExtension; } /** * Sets the file extension used to look for mapping files under. * * @return void */ public function setFileExtension(string $fileExtension) { $this->fileExtension = $fileExtension; } /** * {@inheritDoc} * * Returns whether the class with the specified name is transient. Only non-transient * classes, that is entities and mapped superclasses, should have their metadata loaded. * * @psalm-param class-string $className * * @return bool */ abstract public function isTransient(string $className); /** * Gets the names of all mapped classes known to this driver. * * @return string[] The names of all mapped classes known to this driver. * @psalm-return list<class-string> */ public function getAllClassNames() { if ($this->classNames !== null) { return $this->classNames; } if ($this->paths === []) { throw MappingException::pathRequiredForDriver(static::class); } $classes = []; $includedFiles = []; foreach ($this->paths as $path) { if (! is_dir($path)) { throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); } $iterator = new RegexIterator( new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY ), '/^.+' . preg_quote($this->fileExtension) . '$/i', RecursiveRegexIterator::GET_MATCH ); foreach ($iterator as $file) { $sourceFile = $file[0]; if (preg_match('(^phar:)i', $sourceFile) === 0) { $sourceFile = realpath($sourceFile); } foreach ($this->excludePaths as $excludePath) { $realExcludePath = realpath($excludePath); assert($realExcludePath !== false); $exclude = str_replace('\\', '/', $realExcludePath); $current = str_replace('\\', '/', $sourceFile); if (strpos($current, $exclude) !== false) { continue 2; } } require_once $sourceFile; $includedFiles[] = $sourceFile; } } $declared = get_declared_classes(); foreach ($declared as $className) { $rc = new ReflectionClass($className); $sourceFile = $rc->getFileName(); if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) { continue; } $classes[] = $className; } $this->classNames = $classes; return $classes; } } persistence/src/Persistence/Mapping/Driver/DefaultFileLocator.php 0000644 00000011045 15120025732 0021210 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\MappingException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use function array_merge; use function array_unique; use function assert; use function is_dir; use function is_file; use function is_string; use function str_replace; use const DIRECTORY_SEPARATOR; /** * Locates the file that contains the metadata information for a given class name. * * This behavior is independent of the actual content of the file. It just detects * the file which is responsible for the given class name. */ class DefaultFileLocator implements FileLocator { /** * The paths where to look for mapping files. * * @var array<int, string> */ protected $paths = []; /** * The file extension of mapping documents. * * @var string|null */ protected $fileExtension; /** * Initializes a new FileDriver that looks in the given path(s) for mapping * documents and operates in the specified operating mode. * * @param string|array<int, string> $paths One or multiple paths where mapping documents * can be found. * @param string|null $fileExtension The file extension of mapping documents, * usually prefixed with a dot. */ public function __construct($paths, ?string $fileExtension = null) { $this->addPaths((array) $paths); $this->fileExtension = $fileExtension; } /** * Appends lookup paths to metadata driver. * * @param array<int, string> $paths * * @return void */ public function addPaths(array $paths) { $this->paths = array_unique(array_merge($this->paths, $paths)); } /** * Retrieves the defined metadata lookup paths. * * @return array<int, string> */ public function getPaths() { return $this->paths; } /** * Gets the file extension used to look for mapping files under. * * @return string|null */ public function getFileExtension() { return $this->fileExtension; } /** * Sets the file extension used to look for mapping files under. * * @param string|null $fileExtension The file extension to set. * * @return void */ public function setFileExtension(?string $fileExtension) { $this->fileExtension = $fileExtension; } /** * {@inheritDoc} */ public function findMappingFile(string $className) { $fileName = str_replace('\\', '.', $className) . $this->fileExtension; // Check whether file exists foreach ($this->paths as $path) { if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { return $path . DIRECTORY_SEPARATOR . $fileName; } } throw MappingException::mappingFileNotFound($className, $fileName); } /** * {@inheritDoc} */ public function getAllClassNames(string $globalBasename) { if ($this->paths === []) { return []; } $classes = []; foreach ($this->paths as $path) { if (! is_dir($path)) { throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { $fileName = $file->getBasename($this->fileExtension); if ($fileName === $file->getBasename() || $fileName === $globalBasename) { continue; } // NOTE: All files found here means classes are not transient! assert(is_string($fileName)); /** @psalm-var class-string */ $class = str_replace('.', '\\', $fileName); $classes[] = $class; } } return $classes; } /** * {@inheritDoc} */ public function fileExists(string $className) { $fileName = str_replace('\\', '.', $className) . $this->fileExtension; // Check whether file exists foreach ($this->paths as $path) { if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { return true; } } return false; } } persistence/src/Persistence/Mapping/Driver/FileDriver.php 0000644 00000013400 15120025732 0017530 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\MappingException; use function array_keys; use function array_merge; use function array_unique; use function array_values; use function is_file; use function str_replace; /** * Base driver for file-based metadata drivers. * * A file driver operates in a mode where it loads the mapping files of individual * classes on demand. This requires the user to adhere to the convention of 1 mapping * file per class and the file names of the mapping files must correspond to the full * class name, including namespace, with the namespace delimiters '\', replaced by dots '.'. */ abstract class FileDriver implements MappingDriver { /** @var FileLocator */ protected $locator; /** * @var ClassMetadata[]|null * @psalm-var array<class-string, ClassMetadata<object>>|null */ protected $classCache; /** @var string */ protected $globalBasename = ''; /** * Initializes a new FileDriver that looks in the given path(s) for mapping * documents and operates in the specified operating mode. * * @param string|array<int, string>|FileLocator $locator A FileLocator or one/multiple paths * where mapping documents can be found. */ public function __construct($locator, ?string $fileExtension = null) { if ($locator instanceof FileLocator) { $this->locator = $locator; } else { $this->locator = new DefaultFileLocator((array) $locator, $fileExtension); } } /** * Sets the global basename. * * @return void */ public function setGlobalBasename(string $file) { $this->globalBasename = $file; } /** * Retrieves the global basename. * * @return string|null */ public function getGlobalBasename() { return $this->globalBasename; } /** * Gets the element of schema meta data for the class from the mapping file. * This will lazily load the mapping file if it is not loaded yet. * * @psalm-param class-string $className * * @return ClassMetadata The element of schema meta data. * @psalm-return ClassMetadata<object> * * @throws MappingException */ public function getElement(string $className) { if ($this->classCache === null) { $this->initialize(); } if (isset($this->classCache[$className])) { return $this->classCache[$className]; } $result = $this->loadMappingFile($this->locator->findMappingFile($className)); if (! isset($result[$className])) { throw MappingException::invalidMappingFile( $className, str_replace('\\', '.', $className) . $this->locator->getFileExtension() ); } $this->classCache[$className] = $result[$className]; return $result[$className]; } /** * {@inheritDoc} */ public function isTransient(string $className) { if ($this->classCache === null) { $this->initialize(); } if (isset($this->classCache[$className])) { return false; } return ! $this->locator->fileExists($className); } /** * {@inheritDoc} */ public function getAllClassNames() { if ($this->classCache === null) { $this->initialize(); } if ($this->classCache === []) { return $this->locator->getAllClassNames($this->globalBasename); } /** @psalm-var array<class-string, ClassMetadata<object>> $classCache */ $classCache = $this->classCache; /** @var list<class-string> $keys */ $keys = array_keys($classCache); return array_values(array_unique(array_merge( $keys, $this->locator->getAllClassNames($this->globalBasename) ))); } /** * Loads a mapping file with the given name and returns a map * from class/entity names to their corresponding file driver elements. * * @param string $file The mapping file to load. * * @return ClassMetadata[] * @psalm-return array<class-string, ClassMetadata<object>> */ abstract protected function loadMappingFile(string $file); /** * Initializes the class cache from all the global files. * * Using this feature adds a substantial performance hit to file drivers as * more metadata has to be loaded into memory than might actually be * necessary. This may not be relevant to scenarios where caching of * metadata is in place, however hits very hard in scenarios where no * caching is used. * * @return void */ protected function initialize() { $this->classCache = []; if ($this->globalBasename === null) { return; } foreach ($this->locator->getPaths() as $path) { $file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension(); if (! is_file($file)) { continue; } $this->classCache = array_merge( $this->classCache, $this->loadMappingFile($file) ); } } /** * Retrieves the locator used to discover mapping files by className. * * @return FileLocator */ public function getLocator() { return $this->locator; } /** * Sets the locator used to discover mapping files by className. * * @return void */ public function setLocator(FileLocator $locator) { $this->locator = $locator; } } persistence/src/Persistence/Mapping/Driver/FileLocator.php 0000644 00000002406 15120025732 0017704 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; /** * Locates the file that contains the metadata information for a given class name. * * This behavior is independent of the actual content of the file. It just detects * the file which is responsible for the given class name. */ interface FileLocator { /** * Locates mapping file for the given class name. * * @return string */ public function findMappingFile(string $className); /** * Gets all class names that are found with this file locator. * * @param string $globalBasename Passed to allow excluding the basename. * * @return array<int, string> * @psalm-return list<class-string> */ public function getAllClassNames(string $globalBasename); /** * Checks if a file can be found for this class name. * * @return bool */ public function fileExists(string $className); /** * Gets all the paths that this file locator looks for mapping files. * * @return array<int, string> */ public function getPaths(); /** * Gets the file extension that mapping files are suffixed with. * * @return string|null */ public function getFileExtension(); } persistence/src/Persistence/Mapping/Driver/MappingDriver.php 0000644 00000002150 15120025732 0020244 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\ClassMetadata; /** * Contract for metadata drivers. */ interface MappingDriver { /** * Loads the metadata for the specified class into the provided container. * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @return void * * @template T of object */ public function loadMetadataForClass(string $className, ClassMetadata $metadata); /** * Gets the names of all mapped classes known to this driver. * * @return array<int, string> The names of all mapped classes known to this driver. * @psalm-return list<class-string> */ public function getAllClassNames(); /** * Returns whether the class with the specified name should have its metadata loaded. * This is only the case if it is either mapped as an Entity or a MappedSuperclass. * * @psalm-param class-string $className * * @return bool */ public function isTransient(string $className); } persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php 0000644 00000006347 15120025732 0021223 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\MappingException; use function array_keys; use function spl_object_hash; use function strpos; /** * The DriverChain allows you to add multiple other mapping drivers for * certain namespaces. */ class MappingDriverChain implements MappingDriver { /** * The default driver. * * @var MappingDriver|null */ private $defaultDriver; /** @var array<string, MappingDriver> */ private $drivers = []; /** * Gets the default driver. * * @return MappingDriver|null */ public function getDefaultDriver() { return $this->defaultDriver; } /** * Set the default driver. * * @return void */ public function setDefaultDriver(MappingDriver $driver) { $this->defaultDriver = $driver; } /** * Adds a nested driver. * * @return void */ public function addDriver(MappingDriver $nestedDriver, string $namespace) { $this->drivers[$namespace] = $nestedDriver; } /** * Gets the array of nested drivers. * * @return array<string, MappingDriver> $drivers */ public function getDrivers() { return $this->drivers; } /** * {@inheritDoc} */ public function loadMetadataForClass(string $className, ClassMetadata $metadata) { foreach ($this->drivers as $namespace => $driver) { if (strpos($className, $namespace) === 0) { $driver->loadMetadataForClass($className, $metadata); return; } } if ($this->defaultDriver !== null) { $this->defaultDriver->loadMetadataForClass($className, $metadata); return; } throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); } /** * {@inheritDoc} */ public function getAllClassNames() { $classNames = []; $driverClasses = []; foreach ($this->drivers as $namespace => $driver) { $oid = spl_object_hash($driver); if (! isset($driverClasses[$oid])) { $driverClasses[$oid] = $driver->getAllClassNames(); } foreach ($driverClasses[$oid] as $className) { if (strpos($className, $namespace) !== 0) { continue; } $classNames[$className] = true; } } if ($this->defaultDriver !== null) { foreach ($this->defaultDriver->getAllClassNames() as $className) { $classNames[$className] = true; } } return array_keys($classNames); } /** * {@inheritDoc} */ public function isTransient(string $className) { foreach ($this->drivers as $namespace => $driver) { if (strpos($className, $namespace) === 0) { return $driver->isTransient($className); } } if ($this->defaultDriver !== null) { return $this->defaultDriver->isTransient($className); } return true; } } persistence/src/Persistence/Mapping/Driver/PHPDriver.php 0000644 00000002001 15120025732 0017273 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\ClassMetadata; /** * The PHPDriver includes php files which just populate ClassMetadataInfo * instances with plain PHP code. */ class PHPDriver extends FileDriver { /** * @var ClassMetadata * @psalm-var ClassMetadata<object> */ protected $metadata; /** @param string|array<int, string>|FileLocator $locator */ public function __construct($locator) { parent::__construct($locator, '.php'); } /** * {@inheritDoc} */ public function loadMetadataForClass(string $className, ClassMetadata $metadata) { $this->metadata = $metadata; $this->loadMappingFile($this->locator->findMappingFile($className)); } /** * {@inheritDoc} */ protected function loadMappingFile(string $file) { $metadata = $this->metadata; include $file; return [$metadata->getName() => $metadata]; } } persistence/src/Persistence/Mapping/Driver/StaticPHPDriver.php 0000644 00000006261 15120025732 0020457 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\MappingException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; use function array_merge; use function array_unique; use function get_declared_classes; use function in_array; use function is_dir; use function method_exists; use function realpath; /** * The StaticPHPDriver calls a static loadMetadata() method on your entity * classes where you can manually populate the ClassMetadata instance. */ class StaticPHPDriver implements MappingDriver { /** * Paths of entity directories. * * @var array<int, string> */ private $paths = []; /** * Map of all class names. * * @var array<int, string> * @psalm-var list<class-string> */ private $classNames; /** @param array<int, string>|string $paths */ public function __construct($paths) { $this->addPaths((array) $paths); } /** * @param array<int, string> $paths * * @return void */ public function addPaths(array $paths) { $this->paths = array_unique(array_merge($this->paths, $paths)); } /** * {@inheritdoc} */ public function loadMetadataForClass(string $className, ClassMetadata $metadata) { $className::loadMetadata($metadata); } /** * {@inheritDoc} * * @todo Same code exists in ColocatedMappingDriver, should we re-use it * somehow or not worry about it? */ public function getAllClassNames() { if ($this->classNames !== null) { return $this->classNames; } if ($this->paths === []) { throw MappingException::pathRequiredForDriver(static::class); } $classes = []; $includedFiles = []; foreach ($this->paths as $path) { if (! is_dir($path)) { throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { if ($file->getBasename('.php') === $file->getBasename()) { continue; } $sourceFile = realpath($file->getPathName()); require_once $sourceFile; $includedFiles[] = $sourceFile; } } $declared = get_declared_classes(); foreach ($declared as $className) { $rc = new ReflectionClass($className); $sourceFile = $rc->getFileName(); if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) { continue; } $classes[] = $className; } $this->classNames = $classes; return $classes; } /** * {@inheritdoc} */ public function isTransient(string $className) { return ! method_exists($className, 'loadMetadata'); } } persistence/src/Persistence/Mapping/Driver/SymfonyFileLocator.php 0000644 00000016113 15120025732 0021271 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping\Driver; use Doctrine\Persistence\Mapping\MappingException; use InvalidArgumentException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RuntimeException; use function array_keys; use function array_merge; use function assert; use function is_dir; use function is_file; use function is_int; use function realpath; use function sprintf; use function str_replace; use function strlen; use function strpos; use function strrpos; use function strtr; use function substr; use const DIRECTORY_SEPARATOR; /** * The Symfony File Locator makes a simplifying assumptions compared * to the DefaultFileLocator. By assuming paths only contain entities of a certain * namespace the mapping files consists of the short classname only. */ class SymfonyFileLocator implements FileLocator { /** * The paths where to look for mapping files. * * @var array<int, string> */ protected $paths = []; /** * A map of mapping directory path to namespace prefix used to expand class shortnames. * * @var array<string, string> */ protected $prefixes = []; /** * File extension that is searched for. * * @var string|null */ protected $fileExtension; /** * Represents PHP namespace delimiters when looking for files * * @var string */ private $nsSeparator; /** * @param array<string, string> $prefixes * @param string $nsSeparator String which would be used when converting FQCN * to filename and vice versa. Should not be empty */ public function __construct( array $prefixes, string $fileExtension = '', string $nsSeparator = '.' ) { $this->addNamespacePrefixes($prefixes); $this->fileExtension = $fileExtension; if ($nsSeparator === '') { throw new InvalidArgumentException('Namespace separator should not be empty'); } $this->nsSeparator = $nsSeparator; } /** * Adds Namespace Prefixes. * * @param array<string, string> $prefixes * * @return void */ public function addNamespacePrefixes(array $prefixes) { $this->prefixes = array_merge($this->prefixes, $prefixes); $this->paths = array_merge($this->paths, array_keys($prefixes)); } /** * Gets Namespace Prefixes. * * @return string[] */ public function getNamespacePrefixes() { return $this->prefixes; } /** * {@inheritDoc} */ public function getPaths() { return $this->paths; } /** * {@inheritDoc} */ public function getFileExtension() { return $this->fileExtension; } /** * Sets the file extension used to look for mapping files under. * * @param string $fileExtension The file extension to set. * * @return void */ public function setFileExtension(string $fileExtension) { $this->fileExtension = $fileExtension; } /** * {@inheritDoc} */ public function fileExists(string $className) { $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; foreach ($this->paths as $path) { if (! isset($this->prefixes[$path])) { // global namespace class if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { return true; } continue; } $prefix = $this->prefixes[$path]; if (strpos($className, $prefix . '\\') !== 0) { continue; } $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; if (is_file($filename)) { return true; } } return false; } /** * {@inheritDoc} */ public function getAllClassNames(?string $globalBasename = null) { if ($this->paths === []) { return []; } $classes = []; foreach ($this->paths as $path) { if (! is_dir($path)) { throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { $fileName = $file->getBasename($this->fileExtension); if ($fileName === $file->getBasename() || $fileName === $globalBasename) { continue; } // NOTE: All files found here means classes are not transient! if (isset($this->prefixes[$path])) { // Calculate namespace suffix for given prefix as a relative path from basepath to file path $nsSuffix = strtr( substr($this->realpath($file->getPath()), strlen($this->realpath($path))), $this->nsSeparator, '\\' ); /** @psalm-var class-string */ $class = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName); } else { /** @psalm-var class-string */ $class = str_replace($this->nsSeparator, '\\', $fileName); } $classes[] = $class; } } return $classes; } /** * {@inheritDoc} */ public function findMappingFile(string $className) { $defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension; foreach ($this->paths as $path) { if (! isset($this->prefixes[$path])) { if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) { return $path . DIRECTORY_SEPARATOR . $defaultFileName; } continue; } $prefix = $this->prefixes[$path]; if (strpos($className, $prefix . '\\') !== 0) { continue; } $filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension; if (is_file($filename)) { return $filename; } } $pos = strrpos($className, '\\'); assert(is_int($pos)); throw MappingException::mappingFileNotFound( $className, substr($className, $pos + 1) . $this->fileExtension ); } private function realpath(string $path): string { $realpath = realpath($path); if ($realpath === false) { throw new RuntimeException(sprintf('Could not get realpath for %s', $path)); } return $realpath; } } persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php 0000644 00000034631 15120025732 0021775 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Proxy; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; use ReflectionException; use function array_combine; use function array_keys; use function array_map; use function array_reverse; use function array_unshift; use function assert; use function class_exists; use function ltrim; use function str_replace; use function strpos; use function strrpos; use function substr; /** * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * metadata mapping informations of a class which describes how a class should be mapped * to a relational database. * * This class was abstracted from the ORM ClassMetadataFactory. * * @template CMTemplate of ClassMetadata * @template-implements ClassMetadataFactory<CMTemplate> */ abstract class AbstractClassMetadataFactory implements ClassMetadataFactory { /** * Salt used by specific Object Manager implementation. * * @var string */ protected $cacheSalt = '__CLASSMETADATA__'; /** @var CacheItemPoolInterface|null */ private $cache; /** * @var array<string, ClassMetadata> * @psalm-var CMTemplate[] */ private $loadedMetadata = []; /** @var bool */ protected $initialized = false; /** @var ReflectionService|null */ private $reflectionService = null; /** @var ProxyClassNameResolver|null */ private $proxyClassNameResolver = null; public function setCache(CacheItemPoolInterface $cache): void { $this->cache = $cache; } final protected function getCache(): ?CacheItemPoolInterface { return $this->cache; } /** * Returns an array of all the loaded metadata currently in memory. * * @return ClassMetadata[] * @psalm-return CMTemplate[] */ public function getLoadedMetadata() { return $this->loadedMetadata; } /** * {@inheritDoc} */ public function getAllMetadata() { if (! $this->initialized) { $this->initialize(); } $driver = $this->getDriver(); $metadata = []; foreach ($driver->getAllClassNames() as $className) { $metadata[] = $this->getMetadataFor($className); } return $metadata; } public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void { $this->proxyClassNameResolver = $resolver; } /** * Lazy initialization of this stuff, especially the metadata driver, * since these are not needed at all when a metadata cache is active. * * @return void */ abstract protected function initialize(); /** * Returns the mapping driver implementation. * * @return MappingDriver */ abstract protected function getDriver(); /** * Wakes up reflection after ClassMetadata gets unserialized from cache. * * @psalm-param CMTemplate $class * * @return void */ abstract protected function wakeupReflection( ClassMetadata $class, ReflectionService $reflService ); /** * Initializes Reflection after ClassMetadata was constructed. * * @psalm-param CMTemplate $class * * @return void */ abstract protected function initializeReflection( ClassMetadata $class, ReflectionService $reflService ); /** * Checks whether the class metadata is an entity. * * This method should return false for mapped superclasses or embedded classes. * * @psalm-param CMTemplate $class * * @return bool */ abstract protected function isEntity(ClassMetadata $class); /** * Removes the prepended backslash of a class string to conform with how php outputs class names * * @psalm-param class-string $className * * @psalm-return class-string */ private function normalizeClassName(string $className): string { return ltrim($className, '\\'); } /** * {@inheritDoc} * * @throws ReflectionException * @throws MappingException */ public function getMetadataFor(string $className) { $className = $this->normalizeClassName($className); if (isset($this->loadedMetadata[$className])) { return $this->loadedMetadata[$className]; } if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) { throw MappingException::classIsAnonymous($className); } if (! class_exists($className, false) && strpos($className, ':') !== false) { throw MappingException::nonExistingClass($className); } $realClassName = $this->getRealClass($className); if (isset($this->loadedMetadata[$realClassName])) { // We do not have the alias name in the map, include it return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; } try { if ($this->cache !== null) { $cached = $this->cache->getItem($this->getCacheKey($realClassName))->get(); if ($cached instanceof ClassMetadata) { /** @psalm-var CMTemplate $cached */ $this->loadedMetadata[$realClassName] = $cached; $this->wakeupReflection($cached, $this->getReflectionService()); } else { $loadedMetadata = $this->loadMetadata($realClassName); $classNames = array_combine( array_map([$this, 'getCacheKey'], $loadedMetadata), $loadedMetadata ); foreach ($this->cache->getItems(array_keys($classNames)) as $item) { if (! isset($classNames[$item->getKey()])) { continue; } $item->set($this->loadedMetadata[$classNames[$item->getKey()]]); $this->cache->saveDeferred($item); } $this->cache->commit(); } } else { $this->loadMetadata($realClassName); } } catch (MappingException $loadingException) { $fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName); if ($fallbackMetadataResponse === null) { throw $loadingException; } $this->loadedMetadata[$realClassName] = $fallbackMetadataResponse; } if ($className !== $realClassName) { // We do not have the alias name in the map, include it $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; } return $this->loadedMetadata[$className]; } /** * {@inheritDoc} */ public function hasMetadataFor(string $className) { $className = $this->normalizeClassName($className); return isset($this->loadedMetadata[$className]); } /** * Sets the metadata descriptor for a specific class. * * NOTE: This is only useful in very special cases, like when generating proxy classes. * * @psalm-param class-string $className * @psalm-param CMTemplate $class * * @return void */ public function setMetadataFor(string $className, ClassMetadata $class) { $this->loadedMetadata[$this->normalizeClassName($className)] = $class; } /** * Gets an array of parent classes for the given entity class. * * @psalm-param class-string $name * * @return string[] * @psalm-return list<class-string> */ protected function getParentClasses(string $name) { // Collect parent classes, ignoring transient (not-mapped) classes. $parentClasses = []; foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) { if ($this->getDriver()->isTransient($parentClass)) { continue; } $parentClasses[] = $parentClass; } return $parentClasses; } /** * Loads the metadata of the class in question and all it's ancestors whose metadata * is still not loaded. * * Important: The class $name does not necessarily exist at this point here. * Scenarios in a code-generation setup might have access to XML/YAML * Mapping files without the actual PHP code existing here. That is why the * {@see \Doctrine\Persistence\Mapping\ReflectionService} interface * should be used for reflection. * * @param string $name The name of the class for which the metadata should get loaded. * @psalm-param class-string $name * * @return array<int, string> * @psalm-return list<string> */ protected function loadMetadata(string $name) { if (! $this->initialized) { $this->initialize(); } $loaded = []; $parentClasses = $this->getParentClasses($name); $parentClasses[] = $name; // Move down the hierarchy of parent classes, starting from the topmost class $parent = null; $rootEntityFound = false; $visited = []; $reflService = $this->getReflectionService(); foreach ($parentClasses as $className) { if (isset($this->loadedMetadata[$className])) { $parent = $this->loadedMetadata[$className]; if ($this->isEntity($parent)) { $rootEntityFound = true; array_unshift($visited, $className); } continue; } $class = $this->newClassMetadataInstance($className); $this->initializeReflection($class, $reflService); $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited); $this->loadedMetadata[$className] = $class; $parent = $class; if ($this->isEntity($class)) { $rootEntityFound = true; array_unshift($visited, $className); } $this->wakeupReflection($class, $reflService); $loaded[] = $className; } return $loaded; } /** * Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions * * Override this method to implement a fallback strategy for failed metadata loading * * @return ClassMetadata|null * @psalm-return CMTemplate|null */ protected function onNotFoundMetadata(string $className) { return null; } /** * Actually loads the metadata from the underlying metadata. * * @param bool $rootEntityFound True when there is another entity (non-mapped superclass) class above the current class in the PHP class hierarchy. * @param list<class-string> $nonSuperclassParents All parent class names that are not marked as mapped superclasses, with the direct parent class being the first and the root entity class the last element. * @psalm-param CMTemplate $class * @psalm-param CMTemplate|null $parent * * @return void */ abstract protected function doLoadMetadata( ClassMetadata $class, ?ClassMetadata $parent, bool $rootEntityFound, array $nonSuperclassParents ); /** * Creates a new ClassMetadata instance for the given class name. * * @psalm-param class-string<T> $className * * @return ClassMetadata<T> * @psalm-return CMTemplate * * @template T of object */ abstract protected function newClassMetadataInstance(string $className); /** * {@inheritDoc} */ public function isTransient(string $className) { if (! $this->initialized) { $this->initialize(); } if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) { return false; } if (! class_exists($className, false) && strpos($className, ':') !== false) { throw MappingException::nonExistingClass($className); } /** @psalm-var class-string $className */ return $this->getDriver()->isTransient($className); } /** * Sets the reflectionService. * * @return void */ public function setReflectionService(ReflectionService $reflectionService) { $this->reflectionService = $reflectionService; } /** * Gets the reflection service associated with this metadata factory. * * @return ReflectionService */ public function getReflectionService() { if ($this->reflectionService === null) { $this->reflectionService = new RuntimeReflectionService(); } return $this->reflectionService; } protected function getCacheKey(string $realClassName): string { return str_replace('\\', '__', $realClassName) . $this->cacheSalt; } /** * Gets the real class name of a class name that could be a proxy. * * @psalm-param class-string<Proxy<T>>|class-string<T> $class * * @psalm-return class-string<T> * * @template T of object */ private function getRealClass(string $class): string { if ($this->proxyClassNameResolver === null) { $this->createDefaultProxyClassNameResolver(); } assert($this->proxyClassNameResolver !== null); return $this->proxyClassNameResolver->resolveClassName($class); } private function createDefaultProxyClassNameResolver(): void { $this->proxyClassNameResolver = new class implements ProxyClassNameResolver { /** * @psalm-param class-string<Proxy<T>>|class-string<T> $className * * @psalm-return class-string<T> * * @template T of object */ public function resolveClassName(string $className): string { $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); if ($pos === false) { /** @psalm-var class-string<T> */ return $className; } /** @psalm-var class-string<T> */ return substr($className, $pos + Proxy::MARKER_LENGTH + 2); } }; } } persistence/src/Persistence/Mapping/ClassMetadata.php 0000644 00000007065 15120025732 0016762 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use ReflectionClass; /** * Contract for a Doctrine persistence layer ClassMetadata class to implement. * * @template-covariant T of object */ interface ClassMetadata { /** * Gets the fully-qualified class name of this persistent class. * * @return string * @psalm-return class-string<T> */ public function getName(); /** * Gets the mapped identifier field name. * * The returned structure is an array of the identifier field names. * * @return array<int, string> * @psalm-return list<string> */ public function getIdentifier(); /** * Gets the ReflectionClass instance for this mapped class. * * @return ReflectionClass<T> */ public function getReflectionClass(); /** * Checks if the given field name is a mapped identifier for this class. * * @return bool */ public function isIdentifier(string $fieldName); /** * Checks if the given field is a mapped property for this class. * * @return bool */ public function hasField(string $fieldName); /** * Checks if the given field is a mapped association for this class. * * @return bool */ public function hasAssociation(string $fieldName); /** * Checks if the given field is a mapped single valued association for this class. * * @return bool */ public function isSingleValuedAssociation(string $fieldName); /** * Checks if the given field is a mapped collection valued association for this class. * * @return bool */ public function isCollectionValuedAssociation(string $fieldName); /** * A numerically indexed list of field names of this persistent class. * * This array includes identifier fields if present on this class. * * @return array<int, string> */ public function getFieldNames(); /** * Returns an array of identifier field names numerically indexed. * * @return array<int, string> */ public function getIdentifierFieldNames(); /** * Returns a numerically indexed list of association names of this persistent class. * * This array includes identifier associations if present on this class. * * @return array<int, string> */ public function getAssociationNames(); /** * Returns a type name of this field. * * This type names can be implementation specific but should at least include the php types: * integer, string, boolean, float/double, datetime. * * @return string|null */ public function getTypeOfField(string $fieldName); /** * Returns the target class name of the given association. * * @return string|null * @psalm-return class-string|null */ public function getAssociationTargetClass(string $assocName); /** * Checks if the association is the inverse side of a bidirectional association. * * @return bool */ public function isAssociationInverseSide(string $assocName); /** * Returns the target field of the owning side of the association. * * @return string */ public function getAssociationMappedByTargetField(string $assocName); /** * Returns the identifier of this object as an array with field name as key. * * Has to return an empty array if no identifier isset. * * @return array<string, mixed> */ public function getIdentifierValues(object $object); } persistence/src/Persistence/Mapping/ClassMetadataFactory.php 0000644 00000003160 15120025732 0020302 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; /** * Contract for a Doctrine persistence layer ClassMetadata class to implement. * * @template T of ClassMetadata */ interface ClassMetadataFactory { /** * Forces the factory to load the metadata of all classes known to the underlying * mapping driver. * * @return ClassMetadata[] The ClassMetadata instances of all mapped classes. * @psalm-return list<T> */ public function getAllMetadata(); /** * Gets the class metadata descriptor for a class. * * @param class-string $className The name of the class. * * @return ClassMetadata * @psalm-return T */ public function getMetadataFor(string $className); /** * Checks whether the factory has the metadata for a class loaded already. * * @param class-string $className * * @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise. */ public function hasMetadataFor(string $className); /** * Sets the metadata descriptor for a specific class. * * @param class-string $className * @psalm-param T $class * * @return void */ public function setMetadataFor(string $className, ClassMetadata $class); /** * Returns whether the class with the specified name should have its metadata loaded. * This is only the case if it is either mapped directly or as a MappedSuperclass. * * @psalm-param class-string $className * * @return bool */ public function isTransient(string $className); } persistence/src/Persistence/Mapping/MappingException.php 0000644 00000004447 15120025732 0017527 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use Exception; use function implode; use function sprintf; /** * A MappingException indicates that something is wrong with the mapping setup. */ class MappingException extends Exception { /** * @param array<int, string> $namespaces * * @return self */ public static function classNotFoundInNamespaces( string $className, array $namespaces ) { return new self(sprintf( "The class '%s' was not found in the chain configured namespaces %s", $className, implode(', ', $namespaces) )); } /** @param class-string $driverClassName */ public static function pathRequiredForDriver(string $driverClassName): self { return new self(sprintf( 'Specifying the paths to your entities is required when using %s to retrieve all class names.', $driverClassName )); } /** @return self */ public static function fileMappingDriversRequireConfiguredDirectoryPath( ?string $path = null ) { if ($path !== null) { $path = '[' . $path . ']'; } return new self(sprintf( 'File mapping drivers must have a valid directory path, ' . 'however the given path %s seems to be incorrect!', (string) $path )); } /** @return self */ public static function mappingFileNotFound(string $entityName, string $fileName) { return new self(sprintf( "No mapping file found named '%s' for class '%s'.", $fileName, $entityName )); } /** @return self */ public static function invalidMappingFile(string $entityName, string $fileName) { return new self(sprintf( "Invalid mapping file '%s' for class '%s'.", $fileName, $entityName )); } /** @return self */ public static function nonExistingClass(string $className) { return new self(sprintf("Class '%s' does not exist", $className)); } /** @param class-string $className */ public static function classIsAnonymous(string $className): self { return new self(sprintf('Class "%s" is anonymous', $className)); } } persistence/src/Persistence/Mapping/ProxyClassNameResolver.php 0000644 00000000567 15120025732 0020706 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use Doctrine\Persistence\Proxy; interface ProxyClassNameResolver { /** * @psalm-param class-string<Proxy<T>>|class-string<T> $className * * @psalm-return class-string<T> * * @template T of object */ public function resolveClassName(string $className): string; } persistence/src/Persistence/Mapping/ReflectionService.php 0000644 00000003263 15120025732 0017663 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use ReflectionClass; use ReflectionProperty; /** * Very simple reflection service abstraction. * * This is required inside metadata layers that may require either * static or runtime reflection. */ interface ReflectionService { /** * Returns an array of the parent classes (not interfaces) for the given class. * * @psalm-param class-string $class * * @return string[] * @psalm-return class-string[] * * @throws MappingException */ public function getParentClasses(string $class); /** * Returns the shortname of a class. * * @psalm-param class-string $class * * @return string */ public function getClassShortName(string $class); /** * @psalm-param class-string $class * * @return string */ public function getClassNamespace(string $class); /** * Returns a reflection class instance or null. * * @psalm-param class-string<T> $class * * @return ReflectionClass|null * @psalm-return ReflectionClass<T>|null * * @template T of object */ public function getClass(string $class); /** * Returns an accessible property (setAccessible(true)) or null. * * @psalm-param class-string $class * * @return ReflectionProperty|null */ public function getAccessibleProperty(string $class, string $property); /** * Checks if the class have a public method with the given name. * * @psalm-param class-string $class * * @return bool */ public function hasPublicMethod(string $class, string $method); } persistence/src/Persistence/Mapping/RuntimeReflectionService.php 0000644 00000005111 15120025732 0021221 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use Doctrine\Persistence\Reflection\RuntimeReflectionProperty; use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty; use ReflectionClass; use ReflectionException; use ReflectionMethod; use function array_key_exists; use function assert; use function class_exists; use function class_parents; use function phpversion; use function version_compare; /** * PHP Runtime Reflection Service. */ class RuntimeReflectionService implements ReflectionService { /** @var bool */ private $supportsTypedPropertiesWorkaround; public function __construct() { $this->supportsTypedPropertiesWorkaround = version_compare(phpversion(), '7.4.0') >= 0; } /** * {@inheritDoc} */ public function getParentClasses(string $class) { if (! class_exists($class)) { throw MappingException::nonExistingClass($class); } $parents = class_parents($class); assert($parents !== false); return $parents; } /** * {@inheritDoc} */ public function getClassShortName(string $class) { $reflectionClass = new ReflectionClass($class); return $reflectionClass->getShortName(); } /** * {@inheritDoc} */ public function getClassNamespace(string $class) { $reflectionClass = new ReflectionClass($class); return $reflectionClass->getNamespaceName(); } /** * @psalm-param class-string<T> $class * * @return ReflectionClass * @psalm-return ReflectionClass<T> * * @template T of object */ public function getClass(string $class) { return new ReflectionClass($class); } /** * {@inheritDoc} */ public function getAccessibleProperty(string $class, string $property) { $reflectionProperty = new RuntimeReflectionProperty($class, $property); if ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) { $reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property); } $reflectionProperty->setAccessible(true); return $reflectionProperty; } /** * {@inheritDoc} */ public function hasPublicMethod(string $class, string $method) { try { $reflectionMethod = new ReflectionMethod($class, $method); } catch (ReflectionException $e) { return false; } return $reflectionMethod->isPublic(); } } persistence/src/Persistence/Mapping/StaticReflectionService.php 0000644 00000002633 15120025732 0021033 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Mapping; use function strpos; use function strrev; use function strrpos; use function substr; /** * PHP Runtime Reflection Service. */ class StaticReflectionService implements ReflectionService { /** * {@inheritDoc} */ public function getParentClasses(string $class) { return []; } /** * {@inheritDoc} */ public function getClassShortName(string $class) { $nsSeparatorLastPosition = strrpos($class, '\\'); if ($nsSeparatorLastPosition !== false) { $class = substr($class, $nsSeparatorLastPosition + 1); } return $class; } /** * {@inheritDoc} */ public function getClassNamespace(string $class) { $namespace = ''; if (strpos($class, '\\') !== false) { $namespace = strrev(substr(strrev($class), (int) strpos(strrev($class), '\\') + 1)); } return $namespace; } /** * {@inheritDoc} * * @return null */ public function getClass(string $class) { return null; } /** * {@inheritDoc} */ public function getAccessibleProperty(string $class, string $property) { return null; } /** * {@inheritDoc} */ public function hasPublicMethod(string $class, string $method) { return true; } } persistence/src/Persistence/Reflection/EnumReflectionProperty.php 0000644 00000004457 15120025732 0021441 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; use BackedEnum; use ReflectionProperty; use ReturnTypeWillChange; use function array_map; use function is_array; /** * PHP Enum Reflection Property - special override for backed enums. */ class EnumReflectionProperty extends ReflectionProperty { /** @var ReflectionProperty */ private $originalReflectionProperty; /** @var class-string<BackedEnum> */ private $enumType; /** @param class-string<BackedEnum> $enumType */ public function __construct(ReflectionProperty $originalReflectionProperty, string $enumType) { $this->originalReflectionProperty = $originalReflectionProperty; $this->enumType = $enumType; } /** * {@inheritDoc} * * Converts enum instance to its value. * * @param object|null $object * * @return int|string|int[]|string[]|null */ #[ReturnTypeWillChange] public function getValue($object = null) { if ($object === null) { return null; } $enum = $this->originalReflectionProperty->getValue($object); if ($enum === null) { return null; } return $this->fromEnum($enum); } /** * Converts enum value to enum instance. * * @param object $object * @param mixed $value */ public function setValue($object, $value = null): void { if ($value !== null) { $value = $this->toEnum($value); } $this->originalReflectionProperty->setValue($object, $value); } /** * @param BackedEnum|BackedEnum[] $enum * * @return ($enum is BackedEnum ? (string|int) : (string[]|int[])) */ private function fromEnum($enum) { if (is_array($enum)) { return array_map(static function (BackedEnum $enum) { return $enum->value; }, $enum); } return $enum->value; } /** * @param int|string|int[]|string[] $value * * @return ($value is int|string ? BackedEnum : BackedEnum[]) */ private function toEnum($value) { if (is_array($value)) { return array_map([$this->enumType, 'from'], $value); } return $this->enumType::from($value); } } persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php 0000644 00000003014 15120025732 0023303 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; use Doctrine\Common\Proxy\Proxy; use ReflectionProperty; use ReturnTypeWillChange; /** * PHP Runtime Reflection Public Property - special overrides for public properties. * * @deprecated since version 3.1, use RuntimeReflectionProperty instead. */ class RuntimePublicReflectionProperty extends ReflectionProperty { /** * {@inheritDoc} * * Returns the value of a public property without calling * `__get` on the provided $object if it exists. * * @return mixed */ #[ReturnTypeWillChange] public function getValue($object = null) { return $object !== null ? ((array) $object)[$this->getName()] ?? null : parent::getValue(); } /** * {@inheritDoc} * * Avoids triggering lazy loading via `__set` if the provided object * is a {@see \Doctrine\Common\Proxy\Proxy}. * * @link https://bugs.php.net/bug.php?id=63463 * * @param object|null $object * @param mixed $value * * @return void */ #[ReturnTypeWillChange] public function setValue($object, $value = null) { if (! ($object instanceof Proxy && ! $object->__isInitialized())) { parent::setValue($object, $value); return; } $originalInitializer = $object->__getInitializer(); $object->__setInitializer(null); parent::setValue($object, $value); $object->__setInitializer($originalInitializer); } } persistence/src/Persistence/Reflection/RuntimeReflectionProperty.php 0000644 00000003651 15120025732 0022153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; use Doctrine\Common\Proxy\Proxy as CommonProxy; use Doctrine\Persistence\Proxy; use ReflectionProperty; use ReturnTypeWillChange; use function ltrim; use function method_exists; /** * PHP Runtime Reflection Property. * * Avoids triggering lazy loading if the provided object * is a {@see \Doctrine\Persistence\Proxy}. */ class RuntimeReflectionProperty extends ReflectionProperty { /** @var string */ private $key; public function __construct(string $class, string $name) { parent::__construct($class, $name); $this->key = $this->isPrivate() ? "\0" . ltrim($class, '\\') . "\0" . $name : ($this->isProtected() ? "\0*\0" . $name : $name); } /** * {@inheritDoc} * * @return mixed */ #[ReturnTypeWillChange] public function getValue($object = null) { if ($object === null) { return parent::getValue($object); } return ((array) $object)[$this->key] ?? null; } /** * {@inheritDoc} * * @param object|null $object * @param mixed $value * * @return void */ #[ReturnTypeWillChange] public function setValue($object, $value = null) { if (! ($object instanceof Proxy && ! $object->__isInitialized())) { parent::setValue($object, $value); return; } if ($object instanceof CommonProxy) { $originalInitializer = $object->__getInitializer(); $object->__setInitializer(null); parent::setValue($object, $value); $object->__setInitializer($originalInitializer); return; } if (! method_exists($object, '__setInitialized')) { return; } $object->__setInitialized(true); parent::setValue($object, $value); $object->__setInitialized(false); } } persistence/src/Persistence/Reflection/TypedNoDefaultReflectionProperty.php 0000644 00000000475 15120025732 0023420 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; /** * PHP Typed No Default Reflection Property - special override for typed properties without a default value. */ class TypedNoDefaultReflectionProperty extends RuntimeReflectionProperty { use TypedNoDefaultReflectionPropertyBase; } persistence/src/Persistence/Reflection/TypedNoDefaultReflectionPropertyBase.php 0000644 00000003450 15120025732 0024207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; use Closure; use ReturnTypeWillChange; use function assert; /** * PHP Typed No Default Reflection Property Base - special override for typed properties without a default value. * * @internal since version 3.1 */ trait TypedNoDefaultReflectionPropertyBase { /** * {@inheritDoc} * * Checks that a typed property is initialized before accessing its value. * This is necessary to avoid PHP error "Error: Typed property must not be accessed before initialization". * Should be used only for reflecting typed properties without a default value. * * @param object|null $object * * @return mixed */ #[ReturnTypeWillChange] public function getValue($object = null) { return $object !== null && $this->isInitialized($object) ? parent::getValue($object) : null; } /** * {@inheritDoc} * * Works around the problem with setting typed no default properties to * NULL which is not supported, instead unset() to uninitialize. * * @link https://github.com/doctrine/orm/issues/7999 * * @param object|null $object * * @return void */ #[ReturnTypeWillChange] public function setValue($object, $value = null) { if ($value === null && $this->hasType() && ! $this->getType()->allowsNull()) { $propertyName = $this->getName(); $unsetter = function () use ($propertyName): void { unset($this->$propertyName); }; $unsetter = $unsetter->bindTo($object, $this->getDeclaringClass()->getName()); assert($unsetter instanceof Closure); $unsetter(); return; } parent::setValue($object, $value); } } persistence/src/Persistence/Reflection/TypedNoDefaultRuntimePublicReflectionProperty.php 0000644 00000000671 15120025732 0026121 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence\Reflection; /** * PHP Typed No Default Runtime Public Reflection Property - special override for public typed properties without a default value. * * @deprecated since version 3.1, use TypedNoDefaultReflectionProperty instead. */ class TypedNoDefaultRuntimePublicReflectionProperty extends RuntimePublicReflectionProperty { use TypedNoDefaultReflectionPropertyBase; } persistence/src/Persistence/AbstractManagerRegistry.php 0000644 00000014062 15120025732 0017443 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; use InvalidArgumentException; use ReflectionClass; use function sprintf; /** * Abstract implementation of the ManagerRegistry contract. */ abstract class AbstractManagerRegistry implements ManagerRegistry { /** @var string */ private $name; /** @var array<string, string> */ private $connections; /** @var array<string, string> */ private $managers; /** @var string */ private $defaultConnection; /** @var string */ private $defaultManager; /** * @var string * @psalm-var class-string */ private $proxyInterfaceName; /** * @param array<string, string> $connections * @param array<string, string> $managers * @psalm-param class-string $proxyInterfaceName */ public function __construct( string $name, array $connections, array $managers, string $defaultConnection, string $defaultManager, string $proxyInterfaceName ) { $this->name = $name; $this->connections = $connections; $this->managers = $managers; $this->defaultConnection = $defaultConnection; $this->defaultManager = $defaultManager; $this->proxyInterfaceName = $proxyInterfaceName; } /** * Fetches/creates the given services. * * A service in this context is connection or a manager instance. * * @param string $name The name of the service. * * @return ObjectManager The instance of the given service. */ abstract protected function getService(string $name); /** * Resets the given services. * * A service in this context is connection or a manager instance. * * @param string $name The name of the service. * * @return void */ abstract protected function resetService(string $name); /** * Gets the name of the registry. * * @return string */ public function getName() { return $this->name; } /** * {@inheritdoc} */ public function getConnection(?string $name = null) { if ($name === null) { $name = $this->defaultConnection; } if (! isset($this->connections[$name])) { throw new InvalidArgumentException( sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name) ); } return $this->getService($this->connections[$name]); } /** * {@inheritdoc} */ public function getConnectionNames() { return $this->connections; } /** * {@inheritdoc} */ public function getConnections() { $connections = []; foreach ($this->connections as $name => $id) { $connections[$name] = $this->getService($id); } return $connections; } /** * {@inheritdoc} */ public function getDefaultConnectionName() { return $this->defaultConnection; } /** * {@inheritdoc} */ public function getDefaultManagerName() { return $this->defaultManager; } /** * {@inheritdoc} * * @throws InvalidArgumentException */ public function getManager(?string $name = null) { if ($name === null) { $name = $this->defaultManager; } if (! isset($this->managers[$name])) { throw new InvalidArgumentException( sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name) ); } return $this->getService($this->managers[$name]); } /** * {@inheritDoc} */ public function getManagerForClass(string $class) { $proxyClass = new ReflectionClass($class); if ($proxyClass->isAnonymous()) { return null; } if ($proxyClass->implementsInterface($this->proxyInterfaceName)) { $parentClass = $proxyClass->getParentClass(); if ($parentClass === false) { return null; } $class = $parentClass->getName(); } foreach ($this->managers as $id) { $manager = $this->getService($id); if (! $manager->getMetadataFactory()->isTransient($class)) { return $manager; } } return null; } /** * {@inheritdoc} */ public function getManagerNames() { return $this->managers; } /** * {@inheritdoc} */ public function getManagers() { $managers = []; foreach ($this->managers as $name => $id) { $manager = $this->getService($id); $managers[$name] = $manager; } return $managers; } /** * {@inheritdoc} */ public function getRepository( string $persistentObject, ?string $persistentManagerName = null ) { return $this ->selectManager($persistentObject, $persistentManagerName) ->getRepository($persistentObject); } /** * {@inheritdoc} */ public function resetManager(?string $name = null) { if ($name === null) { $name = $this->defaultManager; } if (! isset($this->managers[$name])) { throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); } // force the creation of a new document manager // if the current one is closed $this->resetService($this->managers[$name]); return $this->getManager($name); } /** @psalm-param class-string $persistentObject */ private function selectManager( string $persistentObject, ?string $persistentManagerName = null ): ObjectManager { if ($persistentManagerName !== null) { return $this->getManager($persistentManagerName); } return $this->getManagerForClass($persistentObject) ?? $this->getManager(); } } persistence/src/Persistence/ConnectionRegistry.php 0000644 00000001663 15120025732 0016507 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; /** * Contract covering connection for a Doctrine persistence layer ManagerRegistry class to implement. */ interface ConnectionRegistry { /** * Gets the default connection name. * * @return string The default connection name. */ public function getDefaultConnectionName(); /** * Gets the named connection. * * @param string|null $name The connection name (null for the default one). * * @return object */ public function getConnection(?string $name = null); /** * Gets an array of all registered connections. * * @return array<string, object> An array of Connection instances. */ public function getConnections(); /** * Gets all connection names. * * @return array<string, string> An array of connection names. */ public function getConnectionNames(); } persistence/src/Persistence/ManagerRegistry.php 0000644 00000005334 15120025732 0015761 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; /** * Contract covering object managers for a Doctrine persistence layer ManagerRegistry class to implement. */ interface ManagerRegistry extends ConnectionRegistry { /** * Gets the default object manager name. * * @return string The default object manager name. */ public function getDefaultManagerName(); /** * Gets a named object manager. * * @param string|null $name The object manager name (null for the default one). * * @return ObjectManager */ public function getManager(?string $name = null); /** * Gets an array of all registered object managers. * * @return array<string, ObjectManager> An array of ObjectManager instances */ public function getManagers(); /** * Resets a named object manager. * * This method is useful when an object manager has been closed * because of a rollbacked transaction AND when you think that * it makes sense to get a new one to replace the closed one. * * Be warned that you will get a brand new object manager as * the existing one is not useable anymore. This means that any * other object with a dependency on this object manager will * hold an obsolete reference. You can inject the registry instead * to avoid this problem. * * @param string|null $name The object manager name (null for the default one). * * @return ObjectManager */ public function resetManager(?string $name = null); /** * Gets all object manager names and associated service IDs. A service ID * is a string that allows to obtain an object manager, typically from a * PSR-11 container. * * @return array<string,string> An array with object manager names as keys, * and service IDs as values. */ public function getManagerNames(); /** * Gets the ObjectRepository for a persistent object. * * @param string $persistentObject The name of the persistent object. * @param string|null $persistentManagerName The object manager name (null for the default one). * @psalm-param class-string<T> $persistentObject * * @return ObjectRepository * @psalm-return ObjectRepository<T> * * @template T of object */ public function getRepository( string $persistentObject, ?string $persistentManagerName = null ); /** * Gets the object manager associated with a given class. * * @param class-string $class A persistent object class name. * * @return ObjectManager|null */ public function getManagerForClass(string $class); } persistence/src/Persistence/NotifyPropertyChanged.php 0000644 00000001222 15120025732 0017135 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; /** * Interface for classes that notify event listeners of changes to their managed properties. * * This interface is implemented by objects that manually want to notify their object manager or * other listeners when properties change, instead of relying on the object manager to compute * property changes itself when changes are to be persisted. */ interface NotifyPropertyChanged { /** * Adds a listener that wants to be notified about property changes. * * @return void */ public function addPropertyChangedListener(PropertyChangedListener $listener); } persistence/src/Persistence/ObjectManager.php 0000644 00000007775 15120025732 0015372 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\ClassMetadataFactory; /** * Contract for a Doctrine persistence layer ObjectManager class to implement. */ interface ObjectManager { /** * Finds an object by its identifier. * * This is just a convenient shortcut for getRepository($className)->find($id). * * @param string $className The class name of the object to find. * @param mixed $id The identity of the object to find. * @psalm-param class-string<T> $className * * @return object|null The found object. * @psalm-return T|null * * @template T of object */ public function find(string $className, $id); /** * Tells the ObjectManager to make an instance managed and persistent. * * The object will be entered into the database as a result of the flush operation. * * NOTE: The persist operation always considers objects that are not yet known to * this ObjectManager as NEW. Do not pass detached objects to the persist operation. * * @param object $object The instance to make managed and persistent. * * @return void */ public function persist(object $object); /** * Removes an object instance. * * A removed object will be removed from the database as a result of the flush operation. * * @param object $object The object instance to remove. * * @return void */ public function remove(object $object); /** * Clears the ObjectManager. All objects that are currently managed * by this ObjectManager become detached. * * @return void */ public function clear(); /** * Detaches an object from the ObjectManager, causing a managed object to * become detached. Unflushed changes made to the object if any * (including removal of the object), will not be synchronized to the database. * Objects which previously referenced the detached object will continue to * reference it. * * @param object $object The object to detach. * * @return void */ public function detach(object $object); /** * Refreshes the persistent state of an object from the database, * overriding any local changes that have not yet been persisted. * * @param object $object The object to refresh. * * @return void */ public function refresh(object $object); /** * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. * * @return void */ public function flush(); /** * Gets the repository for a class. * * @psalm-param class-string<T> $className * * @psalm-return ObjectRepository<T> * * @template T of object */ public function getRepository(string $className); /** * Returns the ClassMetadata descriptor for a class. * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)). * * @psalm-param class-string<T> $className * * @psalm-return ClassMetadata<T> * * @template T of object */ public function getClassMetadata(string $className); /** * Gets the metadata factory used to gather the metadata of classes. * * @psalm-return ClassMetadataFactory<ClassMetadata<object>> */ public function getMetadataFactory(); /** * Helper method to initialize a lazy loading proxy or persistent collection. * * This method is a no-op for other objects. * * @return void */ public function initializeObject(object $obj); /** * Checks if the object is part of the current UnitOfWork and therefore managed. * * @return bool */ public function contains(object $object); } persistence/src/Persistence/ObjectManagerDecorator.php 0000644 00000003514 15120025732 0017220 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\ClassMetadataFactory; /** * Base class to simplify ObjectManager decorators * * @template-covariant TObjectManager of ObjectManager */ abstract class ObjectManagerDecorator implements ObjectManager { /** @var TObjectManager */ protected $wrapped; /** * {@inheritdoc} */ public function find(string $className, $id) { return $this->wrapped->find($className, $id); } public function persist(object $object) { $this->wrapped->persist($object); } public function remove(object $object) { $this->wrapped->remove($object); } public function clear(): void { $this->wrapped->clear(); } public function detach(object $object) { $this->wrapped->detach($object); } public function refresh(object $object) { $this->wrapped->refresh($object); } public function flush() { $this->wrapped->flush(); } /** * {@inheritdoc} */ public function getRepository(string $className) { return $this->wrapped->getRepository($className); } /** * {@inheritdoc} */ public function getClassMetadata(string $className) { return $this->wrapped->getClassMetadata($className); } /** @psalm-return ClassMetadataFactory<ClassMetadata<object>> */ public function getMetadataFactory() { return $this->wrapped->getMetadataFactory(); } public function initializeObject(object $obj) { $this->wrapped->initializeObject($obj); } /** * {@inheritdoc} */ public function contains(object $object) { return $this->wrapped->contains($object); } } persistence/src/Persistence/ObjectRepository.php 0000644 00000003413 15120025732 0016160 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; use UnexpectedValueException; /** * Contract for a Doctrine persistence layer ObjectRepository class to implement. * * @template-covariant T of object */ interface ObjectRepository { /** * Finds an object by its primary key / identifier. * * @param mixed $id The identifier. * * @return object|null The object. * @psalm-return T|null */ public function find($id); /** * Finds all objects in the repository. * * @return array<int, object> The objects. * @psalm-return T[] */ public function findAll(); /** * Finds objects by a set of criteria. * * Optionally sorting and limiting details can be passed. An implementation may throw * an UnexpectedValueException if certain values of the sorting or limiting details are * not supported. * * @param array<string, mixed> $criteria * @param array<string, string>|null $orderBy * @psalm-param array<string, 'asc'|'desc'|'ASC'|'DESC'>|null $orderBy * * @return array<int, object> The objects. * @psalm-return T[] * * @throws UnexpectedValueException */ public function findBy( array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null ); /** * Finds a single object by a set of criteria. * * @param array<string, mixed> $criteria The criteria. * * @return object|null The object. * @psalm-return T|null */ public function findOneBy(array $criteria); /** * Returns the class name of the object managed by the repository. * * @psalm-return class-string<T> */ public function getClassName(); } persistence/src/Persistence/PropertyChangedListener.php 0000644 00000001323 15120025732 0017454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; /** * Contract for classes that are potential listeners of a {@see NotifyPropertyChanged} * implementor. */ interface PropertyChangedListener { /** * Collect information about a property change. * * @param object $sender The object on which the property changed. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property that changed. * @param mixed $newValue The new value of the property that changed. * * @return void */ public function propertyChanged(object $sender, string $propertyName, $oldValue, $newValue); } persistence/src/Persistence/Proxy.php 0000644 00000001376 15120025732 0014001 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Persistence; /** * Interface for proxy classes. * * @template T of object * @method void __setInitialized(bool $initialized) Implementing this method will be mandatory in version 4. */ interface Proxy { /** * Marker for Proxy class names. */ public const MARKER = '__CG__'; /** * Length of the proxy marker. */ public const MARKER_LENGTH = 6; /** * Initializes this proxy if its not yet initialized. * * Acts as a no-op if already initialized. * * @return void */ public function __load(); /** * Returns whether this proxy is initialized or not. * * @return bool */ public function __isInitialized(); } persistence/CONTRIBUTING.md 0000644 00000000540 15120025732 0011315 0 ustar 00 # Circular dependency This package has a development dependency on `doctrine/common`, which has a regular dependency on this package (`^2.0` at the time of writing). To be able to use Composer, one has to let it understand that this is version 2 (even when developing on 3.0.x), as follows: ```shell COMPOSER_ROOT_VERSION=2.0 composer update -v ``` persistence/LICENSE 0000644 00000002051 15120025732 0010070 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. persistence/README.md 0000644 00000001166 15120025732 0010350 0 ustar 00 # Doctrine Persistence [](https://travis-ci.org/doctrine/persistence) [](https://codecov.io/gh/doctrine/persistence/branch/2.1.x) The Doctrine Persistence project is a library that provides common abstractions for object mapper persistence. ## More resources: * [Website](https://www.doctrine-project.org/) * [Documentation](https://www.doctrine-project.org/projects/doctrine-persistence/en/latest/index.html) * [Downloads](https://github.com/doctrine/persistence/releases) persistence/UPGRADE.md 0000644 00000011755 15120025732 0010507 0 ustar 00 Note about upgrading: Doctrine uses static and runtime mechanisms to raise awareness about deprecated code. - Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or Static Analysis tools (like Psalm, phpstan) - Use of our low-overhead runtime deprecation API, details: https://github.com/doctrine/deprecations/ # Upgrade to 3.1 ## Added method `Proxy::__setInitialized()` Classes implementing `Doctrine\Persistence\Proxy` should implement the new method. This method will be added to the interface in 4.0. ## Deprecated `RuntimePublicReflectionProperty` Use `RuntimeReflectionProperty` instead. # Upgrade to 3.0 ## Removed `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()` These methods only make sense when partially clearing the object manager, which is no longer possible. The second argument of the constructor of `OnClearEventArgs` is removed as well. ## BC Break: removed `ObjectManagerAware` Implement active record style functionality directly in your application, by using a `postLoad` event. ## BC Break: removed `AnnotationDriver` Use `ColocatedMappingDriver` instead. ## BC Break: Removed `MappingException::pathRequired()` Use `MappingException::pathRequiredForDriver()` instead. ## BC Break: removed `LifecycleEventArgs::getEntity()` Use `LifecycleEventArgs::getObject()` instead. ## BC Break: removed support for short namespace aliases - `AbstractClassMetadataFactory::getFqcnFromAlias()` is removed. - `ClassMetadataFactory` methods now require their `$className` argument to be an actual FQCN. ## BC Break: removed `ObjectManager::merge()` `ObjectManagerDecorator::merge()` is removed without replacement. ## BC Break: removed support for `doctrine/cache` Removed support for using doctrine/cache for metadata caching. The `setCacheDriver` and `getCacheDriver` methods have been removed from `Doctrine\Persistence\Mapping\AbstractMetadata`. Please use `getCache` and `setCache` with a PSR-6 implementation instead. ## BC Break: changed signatures `$objectName` has been dropped from the signature of `ObjectManager::clear()`. ```diff - public function clear($objectName = null) + public function clear(): void ``` Also, native parameter type declarations have been added on all public APIs. Native return type declarations have not been added so that it is possible to implement types compatible with both 2.x and 3.x. ## BC Break: Removed `PersistentObject` Please implement this functionality directly in your application if you want ActiveRecord style functionality. # Upgrade to 2.5 ## Deprecated `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()` These methods only make sense when partially clearing the object manager, which is deprecated. Passing a second argument to the constructor of `OnClearEventArgs` is deprecated as well. ## Deprecated `ObjectManagerAware` Along with deprecating `PersistentObject`, deprecating `ObjectManagerAware` means deprecating support for active record, which already came with a word of warning. Please implement this directly in your application with a `postLoad` event if you need active record style functionality. ## Deprecated `MappingException::pathRequired()` `MappingException::pathRequiredForDriver()` should be used instead. # Upgrade to 2.4 ## Deprecated `AnnotationDriver` Since attributes were introduced in PHP 8.0, annotations are deprecated. `AnnotationDriver` is an abstract class that is used when implementing concrete annotation drivers in dependent packages. It is deprecated in favor of using `ColocatedMappingDriver` to implement both annotation and attribute based drivers. This will involve implementing `isTransient()` as well as `__construct()` and `getReader()` to retain backward compatibility. # Upgrade to 2.3 ## Deprecated using short namespace alias syntax in favor of `::class` syntax. Before: ```php $objectManager->find('MyPackage:MyClass', $id); $objectManager->createQuery('SELECT u FROM MyPackage:MyClass'); ``` After: ```php $objectManager->find(MyClass::class, $id); $objectManager->createQuery('SELECT u FROM '. MyClass::class); ``` # Upgrade to 2.2 ## Deprecated `doctrine/cache` usage for metadata caching The `setCacheDriver` and `getCacheDriver` methods in `Doctrine\Persistence\Mapping\AbstractMetadata` have been deprecated. Please use `getCache` and `setCache` with a PSR-6 implementation instead. Note that even after switching to PSR-6, `getCacheDriver` will return a cache instance that wraps the PSR-6 cache. Note that if you use a custom implementation of doctrine/cache, the library may not be able to provide a forward compatibility layer. The cache implementation MUST extend the `Doctrine\Common\Cache\CacheProvider` class. # Upgrade to 1.2 ## Deprecated `ObjectManager::merge()` and `ObjectManager::detach()` Please handle merge operations in your application, and use `ObjectManager::clear()` instead. ## Deprecated `PersistentObject` Please implement this functionality directly in your application if you want ActiveRecord style functionality. persistence/composer.json 0000644 00000003717 15120025732 0011617 0 ustar 00 { "name": "doctrine/persistence", "type": "library", "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", "keywords": [ "persistence", "object", "mapper", "orm", "odm" ], "homepage": "https://www.doctrine-project.org/projects/persistence.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}, {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} ], "require": { "php": "^7.2 || ^8.0", "doctrine/event-manager": "^1 || ^2", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "composer/package-versions-deprecated": "^1.11", "phpstan/phpstan": "1.9.4", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", "doctrine/coding-standard": "^11", "doctrine/common": "^3.0", "phpunit/phpunit": "^8.5 || ^9.5", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "vimeo/psalm": "4.30.0 || 5.3.0" }, "conflict": { "doctrine/common": "<2.10" }, "autoload": { "psr-4": { "Doctrine\\Persistence\\": "src/Persistence" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests", "Doctrine\\Tests_PHP74\\": "tests_php74", "Doctrine\\Tests_PHP81\\": "tests_php81" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "composer/package-versions-deprecated": true } } } persistence/psalm-baseline.xml 0000644 00000000625 15120025732 0012506 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="5.3.0@b6faa3e96b8eb50ec71384c53799b8a107236bb6"> <file src="src/Persistence/AbstractManagerRegistry.php"> <PossiblyNullReference occurrences="1"> <code>getName</code> </PossiblyNullReference> <TypeDoesNotContainType occurrences="1"> <code>$parentClass === false</code> </TypeDoesNotContainType> </file> </files> persistence/psalm.phpstub 0000644 00000000623 15120025732 0011611 0 ustar 00 <?php /** * Workaround for psalm to return a class-string when used with a class string * Can be removed when https://github.com/vimeo/psalm/pull/8219 is released * * @psalm-pure * * @return ($string is class-string ? ($characters is '\\' ? class-string : string) : string) * * @psalm-flow ($string) -> return */ function ltrim(string $string, string $characters = " \t\n\r\0\x0B") : string {} common/docs/en/reference/class-loading.rst 0000644 00000022444 15120025732 0014601 0 ustar 00 Class Loading ============= Class loading is an essential part of any PHP application that makes heavy use of classes and interfaces. Unfortunately, a lot of people and projects spend a lot of time and effort on custom and specialized class loading strategies. It can quickly become a pain to understand what is going on when using multiple libraries and/or frameworks, each with its own way to do class loading. Class loading should be simple and it is an ideal candidate for convention over configuration. Overview -------- The Doctrine Common ClassLoader implements a simple and efficient approach to class loading that is easy to understand and use. The implementation is based on the widely used and accepted convention of mapping namespace and class names to a directory structure. This approach is used for example by Symfony2, the Zend Framework and of course, Doctrine. For example, the following class: .. code-block:: php <?php namespace MyProject\Shipping; class ShippingStrategy { ... } resides in the following directory structure: :: src/ /MyProject /Shipping ShippingStrategy.php Note that the name of "src" or the structure above or beside this directory is completely arbitrary. "src" could be named "classes" or "lib" or whatever. The only convention to adhere to is to map namespaces to directories and classes to files named after the class name. Usage ----- To use a Doctrine Common ClassLoader, you first need to load the class file containing the ClassLoader. This is the only class file that actually needs to be loaded explicitly via ``require``. All other classes will be loaded on demand by the configured class loaders. .. code-block:: php <?php use Doctrine\Common\ClassLoader; require '/path/to/Doctrine/Common/ClassLoader.php'; $classLoader = new ClassLoader('MyProject', '/path/to/src'); A ``ClassLoader`` takes two constructor parameters, both optional. In the normal case both arguments are supplied. The first argument specifies the namespace prefix this class loader should be responsible for and the second parameter is the path to the root directory where the classes can be found according to the convention mentioned previously. The class loader in the example above would thus be responsible for all classes under the 'MyProject' namespace and it would look for the class files starting at the directory '/path/to/src'. Also note that the prefix supplied in the first argument need not be a root namespace but can be an arbitrarily nested namespace as well. This allows you to even have the sources of subnamespaces split across different directories. For example, all projects under the Doctrine umbrella reside in the Doctrine namespace, yet the sources for each project usually do not reside under a common root directory. The following is an example of configuring three class loaders, one for each used Doctrine project: .. code-block:: php <?php use Doctrine\Common\ClassLoader; require '/path/to/Doctrine/Common/ClassLoader.php'; $commonLoader = new ClassLoader('Doctrine\Common', '/path/to/common/lib'); $dbalLoader = new ClassLoader('Doctrine\DBAL', '/path/to/dbal/lib'); $ormLoader = new ClassLoader('Doctrine\ORM', '/path/to/orm/lib'); $commonLoader->register(); $dbalLoader->register(); $ormLoader->register(); Do not be afraid of using multiple class loaders. Due to the efficient class loading design you will not incur much overhead from using many class loaders. Take a look at the implementation of ``ClassLoader#loadClass`` to see how simple and efficient the class loading is. The iteration over the installed class loaders happens in C (with the exception of using ``ClassLoader::classExists``). A ClassLoader can be used in the following other variations, however, these are rarely used/needed: - If only the second argument is not supplied, the class loader will be responsible for the namespace prefix given in the first argument and it will rely on the PHP include_path. - If only the first argument is not supplied, the class loader will be responsible for *all* classes and it will try to look up *all* classes starting at the directory given as the second argument. - If both arguments are not supplied, the class loader will be responsible for *all* classes and it will rely on the PHP include_path. File Extension -------------- By default, a ClassLoader uses the ``.php`` file extension for all class files. You can change this behavior, for example to use a ClassLoader to load classes from a library that uses the ".class.php" convention (but it must nevertheless adhere to the directory structure convention!): .. code-block:: php <?php $customLoader = new ClassLoader('CustomLib', '/path/to/custom/lib'); $customLoader->setFileExtension('.class.php'); $customLoader->register(); Namespace Separator ------------------- By default, a ClassLoader uses the ``\`` namespace separator. You can change this behavior, for example to use a ClassLoader to load legacy Zend Framework classes that still use the underscore "_" separator: .. code-block:: php <?php $zend1Loader = new ClassLoader('Zend', '/path/to/zend/lib'); $zend1Loader->setNamespaceSeparator('_'); $zend1Loader->register(); Failing Silently and class_exists ---------------------------------- A lot of class/autoloaders these days try to fail silently when a class file is not found. For the most part this is necessary in order to support using ``class_exists('ClassName', true)`` which is supposed to return a boolean value but triggers autoloading. This is a bad thing as it basically forces class loaders to fail silently, which in turn requires costly file_exists or fopen calls for each class being loaded, even though in at least 99% of the cases this is not necessary (compare the number of class_exists(..., true) invocations to the total number of classes being loaded in a request). The Doctrine Common ClassLoader does not fail silently, by design. It therefore does not need any costly checks for file existence. A ClassLoader is always responsible for all classes with a certain namespace prefix and if a class is requested to be loaded and can not be found this is considered to be a fatal error. This also means that using class_exists(..., true) to check for class existence when using a Doctrine Common ClassLoader is not possible but this is not a bad thing. What class\_exists(..., true) actually means is two things: 1) Check whether the class is already defined/exists (i.e. class_exists(..., false)) and if not 2) check whether a class file can be loaded for that class. In the Doctrine Common ClassLoader the two responsibilities of loading a class and checking for its existence are separated, which can be observed by the existence of the two methods ``loadClass`` and ``canLoadClass``. Thereby ``loadClass`` does not invoke ``canLoadClass`` internally, by design. However, you are free to use it yourself to check whether a class can be loaded and the following code snippet is thus equivalent to class\_exists(..., true): .. code-block:: php <?php // Equivalent to if (('Foo', true)) if there is only 1 class loader to check if (class_exists('Foo', false) || $classLoader->canLoadClass('Foo')) { // ... } The only problem with this is that it is inconvenient as you need to have a reference to the class loaders around (and there are often multiple class loaders in use). Therefore, a simpler alternative exists for the cases in which you really want to ask all installed class loaders whether they can load the class: ``ClassLoader::classExists($className)``: .. code-block:: php <?php // Equivalent to if (class_exists('Foo', true)) if (ClassLoader::classExists('Foo')) { // ... } This static method can basically be used as a drop-in replacement for class_exists(..., true). It iterates over all installed class loaders and asks each of them via ``canLoadClass``, returning early (with TRUE) as soon as one class loader returns TRUE from ``canLoadClass``. If this sounds like it can potentially be rather costly then because that is true but it is exactly the same thing that class_exists(..., true) does under the hood, it triggers a complete interaction of all class/auto loaders. Checking for class existence via invoking autoloading was never a cheap thing to do but now it is more obvious and more importantly, this check is no longer interleaved with regular class loading, which avoids having to check each and every class for existence prior to loading it. The vast majority of classes to be loaded are *not* optional and a failure to load such a class is, and should be, a fatal error. The ClassLoader design reflects this. If you have code that requires the usage of class\_exists(..., true) or ClassLoader::classExists during normal runtime of the application (i.e. on each request) try to refactor your design to avoid it. Summary ------- No matter which class loader you prefer to use (Doctrine classes do not care about how they are loaded), we kindly encourage you to adhere to the simple convention of mapping namespaces and class names to a directory structure. Class loading should be simple, automated and uniform. Time is better invested in actual application development than in designing special directory structures, autoloaders and clever caching strategies for class loading. common/docs/en/index.rst 0000644 00000000214 15120025732 0011221 0 ustar 00 Common Documentation ==================== Welcome to the Doctrine Common Library documentation. .. toctree:: :depth: 2 :glob: * common/src/Proxy/Exception/InvalidArgumentException.php 0000644 00000005662 15120025732 0017371 0 ustar 00 <?php namespace Doctrine\Common\Proxy\Exception; use Doctrine\Persistence\Proxy; use InvalidArgumentException as BaseInvalidArgumentException; use function get_class; use function gettype; use function is_object; use function sprintf; /** * Proxy Invalid Argument Exception. * * @link www.doctrine-project.org */ class InvalidArgumentException extends BaseInvalidArgumentException implements ProxyException { /** @return self */ public static function proxyDirectoryRequired() { return new self('You must configure a proxy directory. See docs for details'); } /** * @param string $className * @param string $proxyNamespace * @psalm-param class-string $className * * @return self */ public static function notProxyClass($className, $proxyNamespace) { return new self(sprintf('The class "%s" is not part of the proxy namespace "%s"', $className, $proxyNamespace)); } /** * @param string $name * * @return self */ public static function invalidPlaceholder($name) { return new self(sprintf('Provided placeholder for "%s" must be either a string or a valid callable', $name)); } /** @return self */ public static function proxyNamespaceRequired() { return new self('You must configure a proxy namespace'); } /** @return self */ public static function unitializedProxyExpected(Proxy $proxy) { return new self(sprintf('Provided proxy of type "%s" must not be initialized.', get_class($proxy))); } /** * @param mixed $callback * * @return self */ public static function invalidClassNotFoundCallback($callback) { $type = is_object($callback) ? get_class($callback) : gettype($callback); return new self(sprintf('Invalid \$notFoundCallback given: must be a callable, "%s" given', $type)); } /** * @param string $className * @psalm-param class-string $className * * @return self */ public static function classMustNotBeAbstract($className) { return new self(sprintf('Unable to create a proxy for an abstract class "%s".', $className)); } /** * @param string $className * @psalm-param class-string $className * * @return self */ public static function classMustNotBeFinal($className) { return new self(sprintf('Unable to create a proxy for a final class "%s".', $className)); } /** * @param string $className * @psalm-param class-string $className * * @return self */ public static function classMustNotBeReadOnly($className) { return new self(sprintf('Unable to create a proxy for a readonly class "%s".', $className)); } /** @param mixed $value */ public static function invalidAutoGenerateMode($value): self { return new self(sprintf('Invalid auto generate mode "%s" given.', $value)); } } common/src/Proxy/Exception/OutOfBoundsException.php 0000644 00000001172 15120025732 0016477 0 ustar 00 <?php namespace Doctrine\Common\Proxy\Exception; use OutOfBoundsException as BaseOutOfBoundsException; use function sprintf; /** * Proxy Invalid Argument Exception. * * @link www.doctrine-project.org */ class OutOfBoundsException extends BaseOutOfBoundsException implements ProxyException { /** * @param string $className * @param string $idField * @psalm-param class-string $className * * @return self */ public static function missingPrimaryKeyValue($className, $idField) { return new self(sprintf('Missing value for primary key %s on %s', $idField, $className)); } } common/src/Proxy/Exception/ProxyException.php 0000644 00000000261 15120025732 0015407 0 ustar 00 <?php namespace Doctrine\Common\Proxy\Exception; /** * Base exception interface for proxy exceptions. * * @link www.doctrine-project.org */ interface ProxyException { } common/src/Proxy/Exception/UnexpectedValueException.php 0000644 00000003324 15120025732 0017372 0 ustar 00 <?php namespace Doctrine\Common\Proxy\Exception; use Throwable; use UnexpectedValueException as BaseUnexpectedValueException; use function sprintf; /** * Proxy Unexpected Value Exception. * * @link www.doctrine-project.org */ class UnexpectedValueException extends BaseUnexpectedValueException implements ProxyException { /** * @param string $proxyDirectory * * @return self */ public static function proxyDirectoryNotWritable($proxyDirectory) { return new self(sprintf('Your proxy directory "%s" must be writable', $proxyDirectory)); } /** * @param string $className * @param string $methodName * @param string $parameterName * @psalm-param class-string $className * * @return self */ public static function invalidParameterTypeHint( $className, $methodName, $parameterName, ?Throwable $previous = null ) { return new self( sprintf( 'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.', $parameterName, $methodName, $className ), 0, $previous ); } /** * @param string $className * @param string $methodName * @psalm-param class-string $className * * @return self */ public static function invalidReturnTypeHint($className, $methodName, ?Throwable $previous = null) { return new self( sprintf( 'The return type of method "%s" in class "%s" is invalid.', $methodName, $className ), 0, $previous ); } } common/src/Proxy/AbstractProxyFactory.php 0000644 00000017274 15120025732 0014622 0 ustar 00 <?php namespace Doctrine\Common\Proxy; use Doctrine\Common\Proxy\Exception\InvalidArgumentException; use Doctrine\Common\Proxy\Exception\OutOfBoundsException; use Doctrine\Common\Util\ClassUtils; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\ClassMetadataFactory; use function class_exists; use function file_exists; use function filemtime; use function in_array; /** * Abstract factory for proxy objects. */ abstract class AbstractProxyFactory { /** * Never autogenerate a proxy and rely that it was generated by some * process before deployment. */ public const AUTOGENERATE_NEVER = 0; /** * Always generates a new proxy in every request. * * This is only sane during development. */ public const AUTOGENERATE_ALWAYS = 1; /** * Autogenerate the proxy class when the proxy file does not exist. * * This strategy causes a file_exists() call whenever any proxy is used the * first time in a request. */ public const AUTOGENERATE_FILE_NOT_EXISTS = 2; /** * Generate the proxy classes using eval(). * * This strategy is only sane for development, and even then it gives me * the creeps a little. */ public const AUTOGENERATE_EVAL = 3; /** * Autogenerate the proxy class when the proxy file does not exist or * when the proxied file changed. * * This strategy causes a file_exists() call whenever any proxy is used the * first time in a request. When the proxied file is changed, the proxy will * be updated. */ public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4; private const AUTOGENERATE_MODES = [ self::AUTOGENERATE_NEVER, self::AUTOGENERATE_ALWAYS, self::AUTOGENERATE_FILE_NOT_EXISTS, self::AUTOGENERATE_EVAL, self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED, ]; /** @var ClassMetadataFactory */ private $metadataFactory; /** @var ProxyGenerator the proxy generator responsible for creating the proxy classes/files. */ private $proxyGenerator; /** @var int Whether to automatically (re)generate proxy classes. */ private $autoGenerate; /** @var ProxyDefinition[] */ private $definitions = []; /** * @param bool|int $autoGenerate * * @throws InvalidArgumentException When auto generate mode is not valid. */ public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate) { $this->proxyGenerator = $proxyGenerator; $this->metadataFactory = $metadataFactory; $this->autoGenerate = (int) $autoGenerate; if (! in_array($this->autoGenerate, self::AUTOGENERATE_MODES, true)) { throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate); } } /** * Gets a reference proxy instance for the entity of the given type and identified by * the given identifier. * * @param string $className * @param array<mixed> $identifier * * @return Proxy * * @throws OutOfBoundsException */ public function getProxy($className, array $identifier) { $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className); $fqcn = $definition->proxyClassName; $proxy = new $fqcn($definition->initializer, $definition->cloner); foreach ($definition->identifierFields as $idField) { if (! isset($identifier[$idField])) { throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField); } $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]); } return $proxy; } /** * Generates proxy classes for all given classes. * * @param ClassMetadata[] $classes The classes (ClassMetadata instances) * for which to generate proxies. * @param string $proxyDir The target directory of the proxy classes. If not specified, the * directory configured on the Configuration of the EntityManager used * by this factory is used. * * @return int Number of generated proxies. */ public function generateProxyClasses(array $classes, $proxyDir = null) { $generated = 0; foreach ($classes as $class) { if ($this->skipClass($class)) { continue; } $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir); $this->proxyGenerator->generateProxyClass($class, $proxyFileName); $generated += 1; } return $generated; } /** * Reset initialization/cloning logic for an un-initialized proxy * * @return Proxy * * @throws InvalidArgumentException */ public function resetUninitializedProxy(Proxy $proxy) { if ($proxy->__isInitialized()) { throw InvalidArgumentException::unitializedProxyExpected($proxy); } $className = ClassUtils::getClass($proxy); $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className); $proxy->__setInitializer($definition->initializer); $proxy->__setCloner($definition->cloner); return $proxy; } /** * Get a proxy definition for the given class name. * * @param string $className * @psalm-param class-string $className * * @return ProxyDefinition */ private function getProxyDefinition($className) { $classMetadata = $this->metadataFactory->getMetadataFor($className); $className = $classMetadata->getName(); // aliases and case sensitivity $this->definitions[$className] = $this->createProxyDefinition($className); $proxyClassName = $this->definitions[$className]->proxyClassName; if (! class_exists($proxyClassName, false)) { $fileName = $this->proxyGenerator->getProxyFileName($className); switch ($this->autoGenerate) { case self::AUTOGENERATE_NEVER: require $fileName; break; case self::AUTOGENERATE_FILE_NOT_EXISTS: if (! file_exists($fileName)) { $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); } require $fileName; break; case self::AUTOGENERATE_ALWAYS: $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); require $fileName; break; case self::AUTOGENERATE_EVAL: $this->proxyGenerator->generateProxyClass($classMetadata, false); break; case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: if (! file_exists($fileName) || filemtime($fileName) < filemtime($classMetadata->getReflectionClass()->getFileName())) { $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); } require $fileName; break; } } return $this->definitions[$className]; } /** * Determine if this class should be skipped during proxy generation. * * @return bool */ abstract protected function skipClass(ClassMetadata $metadata); /** * @param string $className * @psalm-param class-string $className * * @return ProxyDefinition */ abstract protected function createProxyDefinition($className); } common/src/Proxy/Autoloader.php 0000644 00000005617 15120025732 0012562 0 ustar 00 <?php namespace Doctrine\Common\Proxy; use Closure; use Doctrine\Common\Proxy\Exception\InvalidArgumentException; use function call_user_func; use function file_exists; use function is_callable; use function ltrim; use function spl_autoload_register; use function str_replace; use function strlen; use function strpos; use function substr; use const DIRECTORY_SEPARATOR; /** * Special Autoloader for Proxy classes, which are not PSR-0 compliant. * * @internal */ class Autoloader { /** * Resolves proxy class name to a filename based on the following pattern. * * 1. Remove Proxy namespace from class name. * 2. Remove namespace separators from remaining class name. * 3. Return PHP filename from proxy-dir with the result from 2. * * @param string $proxyDir * @param string $proxyNamespace * @param string $className * @psalm-param class-string $className * * @return string * * @throws InvalidArgumentException */ public static function resolveFile($proxyDir, $proxyNamespace, $className) { if (strpos($className, $proxyNamespace) !== 0) { throw InvalidArgumentException::notProxyClass($className, $proxyNamespace); } // remove proxy namespace from class name $classNameRelativeToProxyNamespace = substr($className, strlen($proxyNamespace)); // remove namespace separators from remaining class name $fileName = str_replace('\\', '', $classNameRelativeToProxyNamespace); return $proxyDir . DIRECTORY_SEPARATOR . $fileName . '.php'; } /** * Registers and returns autoloader callback for the given proxy dir and namespace. * * @param string $proxyDir * @param string $proxyNamespace * @param callable|null $notFoundCallback Invoked when the proxy file is not found. * * @return Closure * * @throws InvalidArgumentException */ public static function register($proxyDir, $proxyNamespace, $notFoundCallback = null) { $proxyNamespace = ltrim($proxyNamespace, '\\'); if ($notFoundCallback !== null && ! is_callable($notFoundCallback)) { throw InvalidArgumentException::invalidClassNotFoundCallback($notFoundCallback); } $autoloader = static function ($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) { if ($proxyNamespace === '') { return; } if (strpos($className, $proxyNamespace) !== 0) { return; } $file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); if ($notFoundCallback && ! file_exists($file)) { call_user_func($notFoundCallback, $proxyDir, $proxyNamespace, $className); } require $file; }; spl_autoload_register($autoloader); return $autoloader; } } common/src/Proxy/Proxy.php 0000644 00000003407 15120025732 0011577 0 ustar 00 <?php namespace Doctrine\Common\Proxy; use Closure; use Doctrine\Persistence\Proxy as BaseProxy; /** * Interface for proxy classes. * * @template T of object * @template-extends BaseProxy<T> */ interface Proxy extends BaseProxy { /** * Marks the proxy as initialized or not. * * @param bool $initialized * * @return void */ public function __setInitialized($initialized); /** * Sets the initializer callback to be used when initializing the proxy. That * initializer should accept 3 parameters: $proxy, $method and $params. Those * are respectively the proxy object that is being initialized, the method name * that triggered initialization and the parameters passed to that method. * * @return void */ public function __setInitializer(?Closure $initializer = null); /** * Retrieves the initializer callback used to initialize the proxy. * * @see __setInitializer * * @return Closure|null */ public function __getInitializer(); /** * Sets the callback to be used when cloning the proxy. That initializer should accept * a single parameter, which is the cloned proxy instance itself. * * @return void */ public function __setCloner(?Closure $cloner = null); /** * Retrieves the callback to be used when cloning the proxy. * * @see __setCloner * * @return Closure|null */ public function __getCloner(); /** * Retrieves the list of lazy loaded properties for a given proxy * * @return array<string, mixed> Keys are the property names, and values are the default values * for those properties. */ public function __getLazyProperties(); } common/src/Proxy/ProxyDefinition.php 0000644 00000002160 15120025732 0013603 0 ustar 00 <?php namespace Doctrine\Common\Proxy; use ReflectionProperty; /** * Definition structure how to create a proxy. */ class ProxyDefinition { /** @var string */ public $proxyClassName; /** @var array<string> */ public $identifierFields; /** @var ReflectionProperty[] */ public $reflectionFields; /** @var callable */ public $initializer; /** @var callable */ public $cloner; /** * @param string $proxyClassName * @param array<string> $identifierFields * @param array<string, ReflectionProperty> $reflectionFields * @param callable $initializer * @param callable $cloner */ public function __construct($proxyClassName, array $identifierFields, array $reflectionFields, $initializer, $cloner) { $this->proxyClassName = $proxyClassName; $this->identifierFields = $identifierFields; $this->reflectionFields = $reflectionFields; $this->initializer = $initializer; $this->cloner = $cloner; } } common/src/Proxy/ProxyGenerator.php 0000644 00000115634 15120025732 0013454 0 ustar 00 <?php namespace Doctrine\Common\Proxy; use BackedEnum; use Doctrine\Common\Proxy\Exception\InvalidArgumentException; use Doctrine\Common\Proxy\Exception\UnexpectedValueException; use Doctrine\Common\Util\ClassUtils; use Doctrine\Persistence\Mapping\ClassMetadata; use ReflectionIntersectionType; use ReflectionMethod; use ReflectionNamedType; use ReflectionParameter; use ReflectionProperty; use ReflectionType; use ReflectionUnionType; use function array_combine; use function array_diff; use function array_key_exists; use function array_map; use function array_slice; use function array_unique; use function assert; use function bin2hex; use function call_user_func; use function chmod; use function class_exists; use function dirname; use function explode; use function file; use function file_put_contents; use function get_class; use function implode; use function in_array; use function interface_exists; use function is_callable; use function is_dir; use function is_scalar; use function is_string; use function is_writable; use function lcfirst; use function ltrim; use function method_exists; use function mkdir; use function preg_match; use function preg_match_all; use function preg_replace; use function preg_split; use function random_bytes; use function rename; use function rtrim; use function sprintf; use function str_replace; use function strpos; use function strrev; use function strtolower; use function strtr; use function substr; use function trim; use function var_export; use const DIRECTORY_SEPARATOR; use const PHP_VERSION_ID; use const PREG_SPLIT_DELIM_CAPTURE; /** * This factory is used to generate proxy classes. * It builds proxies from given parameters, a template and class metadata. */ class ProxyGenerator { /** * Used to match very simple id methods that don't need * to be decorated since the identifier is known. */ public const PATTERN_MATCH_ID_METHOD = <<<'EOT' ((?(DEFINE) (?<type>\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*) (?<intersection_type>(?&type)\s*&\s*(?&type)) (?<union_type>(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type))(?:\s*\|\s*(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type)))+) )(?:public\s+)?(?:function\s+%s\s*\(\)\s*)\s*(?::\s*(?:(?&union_type)|(?&intersection_type)|(?:\??(?&type)))\s*)?{\s*return\s*\$this->%s;\s*})i EOT; /** * The namespace that contains all proxy classes. * * @var string */ private $proxyNamespace; /** * The directory that contains all proxy classes. * * @var string */ private $proxyDirectory; /** * Map of callables used to fill in placeholders set in the template. * * @var string[]|callable[] */ protected $placeholders = [ 'baseProxyInterface' => Proxy::class, 'additionalProperties' => '', ]; /** * Template used as a blueprint to generate proxies. * * @var string */ protected $proxyClassTemplate = '<?php namespace <namespace>; <enumUseStatements> /** * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR */ class <proxyShortClassName> extends \<className> implements \<baseProxyInterface> { /** * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with * three parameters, being respectively the proxy object to be initialized, the method that triggered the * initialization process and an array of ordered parameters that were passed to that method. * * @see \Doctrine\Common\Proxy\Proxy::__setInitializer */ public $__initializer__; /** * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object * * @see \Doctrine\Common\Proxy\Proxy::__setCloner */ public $__cloner__; /** * @var boolean flag indicating if this object was already initialized * * @see \Doctrine\Persistence\Proxy::__isInitialized */ public $__isInitialized__ = false; /** * @var array<string, null> properties to be lazy loaded, indexed by property name */ public static $lazyPropertiesNames = <lazyPropertiesNames>; /** * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names * * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties */ public static $lazyPropertiesDefaults = <lazyPropertiesDefaults>; <additionalProperties> <constructorImpl> <magicGet> <magicSet> <magicIsset> <sleepImpl> <wakeupImpl> <cloneImpl> /** * Forces initialization of the proxy */ public function __load(): void { $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []); } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic */ public function __isInitialized(): bool { return $this->__isInitialized__; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic */ public function __setInitialized($initialized): void { $this->__isInitialized__ = $initialized; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic */ public function __setInitializer(\Closure $initializer = null): void { $this->__initializer__ = $initializer; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic */ public function __getInitializer(): ?\Closure { return $this->__initializer__; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic */ public function __setCloner(\Closure $cloner = null): void { $this->__cloner__ = $cloner; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific cloning logic */ public function __getCloner(): ?\Closure { return $this->__cloner__; } /** * {@inheritDoc} * @internal generated method: use only when explicitly handling proxy specific loading logic * @deprecated no longer in use - generated code now relies on internal components rather than generated public API * @static */ public function __getLazyProperties(): array { return self::$lazyPropertiesDefaults; } <methods> } '; /** * Initializes a new instance of the <tt>ProxyFactory</tt> class that is * connected to the given <tt>EntityManager</tt>. * * @param string $proxyDirectory The directory to use for the proxy classes. It must exist. * @param string $proxyNamespace The namespace to use for the proxy classes. * * @throws InvalidArgumentException */ public function __construct($proxyDirectory, $proxyNamespace) { if (! $proxyDirectory) { throw InvalidArgumentException::proxyDirectoryRequired(); } if (! $proxyNamespace) { throw InvalidArgumentException::proxyNamespaceRequired(); } $this->proxyDirectory = $proxyDirectory; $this->proxyNamespace = $proxyNamespace; } /** * Sets a placeholder to be replaced in the template. * * @param string $name * @param string|callable $placeholder * * @throws InvalidArgumentException */ public function setPlaceholder($name, $placeholder) { if (! is_string($placeholder) && ! is_callable($placeholder)) { throw InvalidArgumentException::invalidPlaceholder($name); } $this->placeholders[$name] = $placeholder; } /** * Sets the base template used to create proxy classes. * * @param string $proxyClassTemplate */ public function setProxyClassTemplate($proxyClassTemplate) { $this->proxyClassTemplate = (string) $proxyClassTemplate; } /** * Generates a proxy class file. * * @param ClassMetadata $class Metadata for the original class. * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used. * * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function generateProxyClass(ClassMetadata $class, $fileName = false) { $this->verifyClassCanBeProxied($class); preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); $placeholders = []; foreach ($placeholderMatches as $placeholder => $name) { $placeholders[$placeholder] = $this->placeholders[$name] ?? [$this, 'generate' . $name]; } foreach ($placeholders as & $placeholder) { if (! is_callable($placeholder)) { continue; } $placeholder = call_user_func($placeholder, $class); } $proxyCode = strtr($this->proxyClassTemplate, $placeholders); if (! $fileName) { $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); if (! class_exists($proxyClassName)) { eval(substr($proxyCode, 5)); } return; } $parentDirectory = dirname($fileName); if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) { throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); } if (! is_writable($parentDirectory)) { throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); } $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); file_put_contents($tmpFileName, $proxyCode); @chmod($tmpFileName, 0664); rename($tmpFileName, $fileName); } /** @throws InvalidArgumentException */ private function verifyClassCanBeProxied(ClassMetadata $class) { if ($class->getReflectionClass()->isFinal()) { throw InvalidArgumentException::classMustNotBeFinal($class->getName()); } if ($class->getReflectionClass()->isAbstract()) { throw InvalidArgumentException::classMustNotBeAbstract($class->getName()); } if (PHP_VERSION_ID >= 80200 && $class->getReflectionClass()->isReadOnly()) { throw InvalidArgumentException::classMustNotBeReadOnly($class->getName()); } } /** * Generates the proxy short class name to be used in the template. * * @return string */ private function generateProxyShortClassName(ClassMetadata $class) { $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); $parts = explode('\\', strrev($proxyClassName), 2); return strrev($parts[0]); } /** * Generates the proxy namespace. * * @return string */ private function generateNamespace(ClassMetadata $class) { $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); $parts = explode('\\', strrev($proxyClassName), 2); return strrev($parts[1]); } /** * Enums must have a use statement when used as public property defaults. */ public function generateEnumUseStatements(ClassMetadata $class): string { if (PHP_VERSION_ID < 80100) { return "\n"; } $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); $lazyLoadedPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $enumClasses = []; foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $name = $property->getName(); if (! in_array($name, $lazyLoadedPublicProperties, true)) { continue; } if (array_key_exists($name, $defaultProperties) && $defaultProperties[$name] instanceof BackedEnum) { $enumClassNameParts = explode('\\', get_class($defaultProperties[$name])); $enumClasses[] = $enumClassNameParts[0]; } } return implode( "\n", array_map( static function ($className) { return 'use ' . $className . ';'; }, array_unique($enumClasses) ) ) . "\n"; } /** * Generates the original class name. * * @return string */ private function generateClassName(ClassMetadata $class) { return ltrim($class->getName(), '\\'); } /** * Generates the array representation of lazy loaded public properties and their default values. * * @return string */ private function generateLazyPropertiesNames(ClassMetadata $class) { $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $values = []; foreach ($lazyPublicProperties as $name) { $values[$name] = null; } return var_export($values, true); } /** * Generates the array representation of lazy loaded public properties names. * * @return string */ private function generateLazyPropertiesDefaults(ClassMetadata $class) { return var_export($this->getLazyLoadedPublicProperties($class), true); } /** * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values). * * @return string */ private function generateConstructorImpl(ClassMetadata $class) { $constructorImpl = <<<'EOT' public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) { EOT; $toUnset = array_map(static function (string $name): string { return '$this->' . $name; }, $this->getLazyLoadedPublicPropertiesNames($class)); return $constructorImpl . ($toUnset === [] ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") . <<<'EOT' $this->__initializer__ = $initializer; $this->__cloner__ = $cloner; } EOT; } /** * Generates the magic getter invoked when lazy loaded public properties are requested. * * @return string */ private function generateMagicGet(ClassMetadata $class) { $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $reflectionClass = $class->getReflectionClass(); $hasParentGet = false; $returnReference = ''; $inheritDoc = ''; $name = '$name'; $parametersString = '$name'; $returnTypeHint = null; if ($reflectionClass->hasMethod('__get')) { $hasParentGet = true; $inheritDoc = '{@inheritDoc}'; $methodReflection = $reflectionClass->getMethod('__get'); if ($methodReflection->returnsReference()) { $returnReference = '& '; } $methodParameters = $methodReflection->getParameters(); $name = '$' . $methodParameters[0]->getName(); $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); $returnTypeHint = $this->getMethodReturnType($methodReflection); } if (empty($lazyPublicProperties) && ! $hasParentGet) { return ''; } $magicGet = <<<EOT /** * $inheritDoc * @param string \$name */ public function {$returnReference}__get($parametersString)$returnTypeHint { EOT; if (! empty($lazyPublicProperties)) { $magicGet .= <<<'EOT' if (\array_key_exists($name, self::$lazyPropertiesNames)) { $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); EOT; if ($returnTypeHint === ': void') { $magicGet .= "\n return;"; } else { $magicGet .= "\n return \$this->\$name;"; } $magicGet .= <<<'EOT' } EOT; } if ($hasParentGet) { $magicGet .= <<<'EOT' $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); EOT; if ($returnTypeHint === ': void') { $magicGet .= <<<'EOT' parent::__get($name); return; EOT; } elseif ($returnTypeHint === ': never') { $magicGet .= <<<'EOT' parent::__get($name); EOT; } else { $magicGet .= <<<'EOT' return parent::__get($name); EOT; } } else { $magicGet .= sprintf(<<<EOT trigger_error(sprintf('Undefined property: %%s::$%%s', __CLASS__, %s), E_USER_NOTICE); EOT , $name); } return $magicGet . "\n }"; } /** * Generates the magic setter (currently unused). * * @return string */ private function generateMagicSet(ClassMetadata $class) { $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $reflectionClass = $class->getReflectionClass(); $hasParentSet = false; $inheritDoc = ''; $parametersString = '$name, $value'; $returnTypeHint = null; if ($reflectionClass->hasMethod('__set')) { $hasParentSet = true; $inheritDoc = '{@inheritDoc}'; $methodReflection = $reflectionClass->getMethod('__set'); $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name', 'value']); $returnTypeHint = $this->getMethodReturnType($methodReflection); } if (empty($lazyPublicProperties) && ! $hasParentSet) { return ''; } $magicSet = <<<EOT /** * $inheritDoc * @param string \$name * @param mixed \$value */ public function __set($parametersString)$returnTypeHint { EOT; if (! empty($lazyPublicProperties)) { $magicSet .= <<<'EOT' if (\array_key_exists($name, self::$lazyPropertiesNames)) { $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); $this->$name = $value; return; } EOT; } if ($hasParentSet) { $magicSet .= <<<'EOT' $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); EOT; if ($returnTypeHint === ': void') { $magicSet .= <<<'EOT' parent::__set($name, $value); return; EOT; } elseif ($returnTypeHint === ': never') { $magicSet .= <<<'EOT' parent::__set($name, $value); EOT; } else { $magicSet .= <<<'EOT' return parent::__set($name, $value); EOT; } } else { $magicSet .= ' $this->$name = $value;'; } return $magicSet . "\n }"; } /** * Generates the magic issetter invoked when lazy loaded public properties are checked against isset(). * * @return string */ private function generateMagicIsset(ClassMetadata $class) { $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); $parametersString = '$name'; $returnTypeHint = null; if ($hasParentIsset) { $methodReflection = $class->getReflectionClass()->getMethod('__isset'); $parametersString = $this->buildParametersString($methodReflection->getParameters(), ['name']); $returnTypeHint = $this->getMethodReturnType($methodReflection); } if (empty($lazyPublicProperties) && ! $hasParentIsset) { return ''; } $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; $magicIsset = <<<EOT /** * $inheritDoc * @param string \$name * @return boolean */ public function __isset($parametersString)$returnTypeHint { EOT; if (! empty($lazyPublicProperties)) { $magicIsset .= <<<'EOT' if (\array_key_exists($name, self::$lazyPropertiesNames)) { $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); return isset($this->$name); } EOT; } if ($hasParentIsset) { $magicIsset .= <<<'EOT' $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); return parent::__isset($name); EOT; } else { $magicIsset .= ' return false;'; } return $magicIsset . "\n }"; } /** * Generates implementation for the `__sleep` method of proxies. * * @return string */ private function generateSleepImpl(ClassMetadata $class) { $reflectionClass = $class->getReflectionClass(); $hasParentSleep = $reflectionClass->hasMethod('__sleep'); $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; $returnTypeHint = $hasParentSleep ? $this->getMethodReturnType($reflectionClass->getMethod('__sleep')) : ''; $sleepImpl = <<<EOT /** * $inheritDoc * @return array */ public function __sleep()$returnTypeHint { EOT; if ($hasParentSleep) { return $sleepImpl . <<<'EOT' $properties = array_merge(['__isInitialized__'], parent::__sleep()); if ($this->__isInitialized__) { $properties = array_diff($properties, array_keys(self::$lazyPropertiesNames)); } return $properties; } EOT; } $allProperties = ['__isInitialized__']; foreach ($class->getReflectionClass()->getProperties() as $prop) { assert($prop instanceof ReflectionProperty); if ($prop->isStatic()) { continue; } $allProperties[] = $prop->isPrivate() ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName() : $prop->getName(); } $lazyPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $protectedProperties = array_diff($allProperties, $lazyPublicProperties); foreach ($allProperties as &$property) { $property = var_export($property, true); } foreach ($protectedProperties as &$property) { $property = var_export($property, true); } $allProperties = implode(', ', $allProperties); $protectedProperties = implode(', ', $protectedProperties); return $sleepImpl . <<<EOT if (\$this->__isInitialized__) { return [$allProperties]; } return [$protectedProperties]; } EOT; } /** * Generates implementation for the `__wakeup` method of proxies. * * @return string */ private function generateWakeupImpl(ClassMetadata $class) { $reflectionClass = $class->getReflectionClass(); $hasParentWakeup = $reflectionClass->hasMethod('__wakeup'); $unsetPublicProperties = []; foreach ($this->getLazyLoadedPublicPropertiesNames($class) as $lazyPublicProperty) { $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; } $shortName = $this->generateProxyShortClassName($class); $inheritDoc = $hasParentWakeup ? '{@inheritDoc}' : ''; $returnTypeHint = $hasParentWakeup ? $this->getMethodReturnType($reflectionClass->getMethod('__wakeup')) : ''; $wakeupImpl = <<<EOT /** * $inheritDoc */ public function __wakeup()$returnTypeHint { if ( ! \$this->__isInitialized__) { \$this->__initializer__ = function ($shortName \$proxy) { \$proxy->__setInitializer(null); \$proxy->__setCloner(null); \$existingProperties = get_object_vars(\$proxy); foreach (\$proxy::\$lazyPropertiesDefaults as \$property => \$defaultValue) { if ( ! array_key_exists(\$property, \$existingProperties)) { \$proxy->\$property = \$defaultValue; } } }; EOT; if (! empty($unsetPublicProperties)) { $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ');'; } $wakeupImpl .= "\n }"; if ($hasParentWakeup) { $wakeupImpl .= "\n parent::__wakeup();"; } $wakeupImpl .= "\n }"; return $wakeupImpl; } /** * Generates implementation for the `__clone` method of proxies. * * @return string */ private function generateCloneImpl(ClassMetadata $class) { $reflectionClass = $class->getReflectionClass(); $hasParentClone = $reflectionClass->hasMethod('__clone'); $returnTypeHint = $hasParentClone ? $this->getMethodReturnType($reflectionClass->getMethod('__clone')) : ''; $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; return <<<EOT /** * $inheritDoc */ public function __clone()$returnTypeHint { \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []); $callParentClone } EOT; } /** * Generates decorated methods by picking those available in the parent class. * * @return string */ private function generateMethods(ClassMetadata $class) { $methods = ''; $methodNames = []; $reflectionMethods = $class->getReflectionClass()->getMethods(ReflectionMethod::IS_PUBLIC); $skippedMethods = [ '__sleep' => true, '__clone' => true, '__wakeup' => true, '__get' => true, '__set' => true, '__isset' => true, ]; foreach ($reflectionMethods as $method) { $name = $method->getName(); if ( $method->isConstructor() || isset($skippedMethods[strtolower($name)]) || isset($methodNames[$name]) || $method->isFinal() || $method->isStatic() || ( ! $method->isPublic()) ) { continue; } $methodNames[$name] = true; $methods .= "\n /**\n" . " * {@inheritDoc}\n" . " */\n" . ' public function '; if ($method->returnsReference()) { $methods .= '&'; } $methods .= $name . '(' . $this->buildParametersString($method->getParameters()) . ')'; $methods .= $this->getMethodReturnType($method); $methods .= "\n" . ' {' . "\n"; if ($this->isShortIdentifierGetter($method, $class)) { $identifier = lcfirst(substr($name, 3)); $fieldType = $class->getTypeOfField($identifier); $cast = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : ''; $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; $methods .= ' '; $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : ''; $methods .= $cast . ' parent::' . $method->getName() . "();\n"; $methods .= ' }' . "\n\n"; } $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); $methods .= "\n \$this->__initializer__ " . '&& $this->__initializer__->__invoke($this, ' . var_export($name, true) . ', [' . $invokeParamsString . ']);' . "\n\n " . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '') . 'parent::' . $name . '(' . $callParamsString . ');' . "\n" . ' }' . "\n"; } return $methods; } /** * Generates the Proxy file name. * * @param string $className * @param string $baseDirectory Optional base directory for proxy file name generation. * If not specified, the directory configured on the Configuration of the * EntityManager will be used by this factory. * @psalm-param class-string $className * * @return string */ public function getProxyFileName($className, $baseDirectory = null) { $baseDirectory = $baseDirectory ?: $this->proxyDirectory; return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER . str_replace('\\', '', $className) . '.php'; } /** * Checks if the method is a short identifier getter. * * What does this mean? For proxy objects the identifier is already known, * however accessing the getter for this identifier usually triggers the * lazy loading, leading to a query that may not be necessary if only the * ID is interesting for the userland code (for example in views that * generate links to the entity, but do not display anything else). * * @param ReflectionMethod $method * * @return bool */ private function isShortIdentifierGetter($method, ClassMetadata $class) { $identifier = lcfirst(substr($method->getName(), 3)); $startLine = $method->getStartLine(); $endLine = $method->getEndLine(); $cheapCheck = $method->getNumberOfParameters() === 0 && substr($method->getName(), 0, 3) === 'get' && in_array($identifier, $class->getIdentifier(), true) && $class->hasField($identifier) && ($endLine - $startLine <= 4); if ($cheapCheck) { $code = file($method->getFileName()); $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); if (preg_match($pattern, $code)) { return true; } } return false; } /** * Generates the list of public properties to be lazy loaded. * * @return array<int, string> */ private function getLazyLoadedPublicPropertiesNames(ClassMetadata $class): array { $properties = []; foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $name = $property->getName(); if ((! $class->hasField($name) && ! $class->hasAssociation($name)) || $class->isIdentifier($name)) { continue; } $properties[] = $name; } return $properties; } /** * Generates the list of default values of public properties. * * @return mixed[] */ private function getLazyLoadedPublicProperties(ClassMetadata $class) { $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); $lazyLoadedPublicProperties = $this->getLazyLoadedPublicPropertiesNames($class); $defaultValues = []; foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $name = $property->getName(); if (! in_array($name, $lazyLoadedPublicProperties, true)) { continue; } if (array_key_exists($name, $defaultProperties)) { $defaultValues[$name] = $defaultProperties[$name]; } elseif (method_exists($property, 'getType')) { $propertyType = $property->getType(); if ($propertyType !== null && $propertyType->allowsNull()) { $defaultValues[$name] = null; } } } return $defaultValues; } /** * @param ReflectionParameter[] $parameters * @param string[] $renameParameters * * @return string */ private function buildParametersString(array $parameters, array $renameParameters = []) { $parameterDefinitions = []; $i = -1; foreach ($parameters as $param) { assert($param instanceof ReflectionParameter); $i++; $parameterDefinition = ''; $parameterType = $this->getParameterType($param); if ($parameterType !== null) { $parameterDefinition .= $parameterType . ' '; } if ($param->isPassedByReference()) { $parameterDefinition .= '&'; } if ($param->isVariadic()) { $parameterDefinition .= '...'; } $parameterDefinition .= '$' . ($renameParameters ? $renameParameters[$i] : $param->getName()); $parameterDefinition .= $this->getParameterDefaultValue($param); $parameterDefinitions[] = $parameterDefinition; } return implode(', ', $parameterDefinitions); } /** @return string|null */ private function getParameterType(ReflectionParameter $parameter) { if (! $parameter->hasType()) { return null; } $declaringFunction = $parameter->getDeclaringFunction(); assert($declaringFunction instanceof ReflectionMethod); return $this->formatType($parameter->getType(), $declaringFunction, $parameter); } /** @return string */ private function getParameterDefaultValue(ReflectionParameter $parameter) { if (! $parameter->isDefaultValueAvailable()) { return ''; } if (PHP_VERSION_ID < 80100 || is_scalar($parameter->getDefaultValue())) { return ' = ' . var_export($parameter->getDefaultValue(), true); } $value = rtrim(substr(explode('$' . $parameter->getName() . ' = ', (string) $parameter, 2)[1], 0, -2)); if (strpos($value, '\\') !== false || strpos($value, '::') !== false) { $value = preg_split("/('(?:[^'\\\\]*+(?:\\\\.)*+)*+')/", $value, -1, PREG_SPLIT_DELIM_CAPTURE); foreach ($value as $i => $part) { if ($i % 2 === 0) { $value[$i] = preg_replace('/(?<![a-zA-Z0-9_\x7f-\xff\\\\])[a-zA-Z0-9_\x7f-\xff]++(?:\\\\[a-zA-Z0-9_\x7f-\xff]++|::)++/', '\\\\\0', $part); } } $value = implode('', $value); } return ' = ' . $value; } /** * @param ReflectionParameter[] $parameters * * @return string[] */ private function getParameterNamesForInvoke(array $parameters) { return array_map( static function (ReflectionParameter $parameter) { return '$' . $parameter->getName(); }, $parameters ); } /** * @param ReflectionParameter[] $parameters * * @return string[] */ private function getParameterNamesForParentCall(array $parameters) { return array_map( static function (ReflectionParameter $parameter) { $name = ''; if ($parameter->isVariadic()) { $name .= '...'; } $name .= '$' . $parameter->getName(); return $name; }, $parameters ); } /** @return string */ private function getMethodReturnType(ReflectionMethod $method) { if (! $method->hasReturnType()) { return ''; } return ': ' . $this->formatType($method->getReturnType(), $method); } /** @return bool */ private function shouldProxiedMethodReturn(ReflectionMethod $method) { if (! $method->hasReturnType()) { return true; } return ! in_array( strtolower($this->formatType($method->getReturnType(), $method)), ['void', 'never'], true ); } /** @return string */ private function formatType( ReflectionType $type, ReflectionMethod $method, ?ReflectionParameter $parameter = null ) { if ($type instanceof ReflectionUnionType) { return implode('|', array_map( function (ReflectionType $unionedType) use ($method, $parameter) { if ($unionedType instanceof ReflectionIntersectionType) { return '(' . $this->formatType($unionedType, $method, $parameter) . ')'; } return $this->formatType($unionedType, $method, $parameter); }, $type->getTypes() )); } if ($type instanceof ReflectionIntersectionType) { return implode('&', array_map( function (ReflectionType $intersectedType) use ($method, $parameter) { return $this->formatType($intersectedType, $method, $parameter); }, $type->getTypes() )); } assert($type instanceof ReflectionNamedType); $name = $type->getName(); $nameLower = strtolower($name); if ($nameLower === 'static') { $name = 'static'; } if ($nameLower === 'self') { $name = $method->getDeclaringClass()->getName(); } if ($nameLower === 'parent') { $name = $method->getDeclaringClass()->getParentClass()->getName(); } if (! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name) && $name !== 'static') { if ($parameter !== null) { throw UnexpectedValueException::invalidParameterTypeHint( $method->getDeclaringClass()->getName(), $method->getName(), $parameter->getName() ); } throw UnexpectedValueException::invalidReturnTypeHint( $method->getDeclaringClass()->getName(), $method->getName() ); } if (! $type->isBuiltin() && $name !== 'static') { $name = '\\' . $name; } if ( $type->allowsNull() && ! in_array($name, ['mixed', 'null'], true) && ($parameter === null || ! $parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== null) ) { $name = '?' . $name; } return $name; } } common/src/Util/ClassUtils.php 0000644 00000005310 15120025732 0012333 0 ustar 00 <?php namespace Doctrine\Common\Util; use Doctrine\Persistence\Proxy; use ReflectionClass; use function get_class; use function get_parent_class; use function ltrim; use function rtrim; use function strrpos; use function substr; /** * Class and reflection related functionality for objects that * might or not be proxy objects at the moment. */ class ClassUtils { /** * Gets the real class name of a class name that could be a proxy. * * @param string $className * @psalm-param class-string<Proxy<T>>|class-string<T> $className * * @return string * @psalm-return class-string<T> * * @template T of object */ public static function getRealClass($className) { $pos = strrpos($className, '\\' . Proxy::MARKER . '\\'); if ($pos === false) { /** @psalm-var class-string<T> */ return $className; } return substr($className, $pos + Proxy::MARKER_LENGTH + 2); } /** * Gets the real class name of an object (even if its a proxy). * * @param object $object * @psalm-param Proxy<T>|T $object * * @return string * @psalm-return class-string<T> * * @template T of object */ public static function getClass($object) { return self::getRealClass(get_class($object)); } /** * Gets the real parent class name of a class or object. * * @param string $className * @psalm-param class-string $className * * @return string * @psalm-return class-string */ public static function getParentClass($className) { return get_parent_class(self::getRealClass($className)); } /** * Creates a new reflection class. * * @param string $className * @psalm-param class-string $className * * @return ReflectionClass */ public static function newReflectionClass($className) { return new ReflectionClass(self::getRealClass($className)); } /** * Creates a new reflection object. * * @param object $object * * @return ReflectionClass */ public static function newReflectionObject($object) { return self::newReflectionClass(self::getClass($object)); } /** * Given a class name and a proxy namespace returns the proxy name. * * @param string $className * @param string $proxyNamespace * @psalm-param class-string $className * * @return string * @psalm-return class-string */ public static function generateProxyClassName($className, $proxyNamespace) { return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); } } common/src/Util/Debug.php 0000644 00000011151 15120025732 0011273 0 ustar 00 <?php namespace Doctrine\Common\Util; use ArrayIterator; use ArrayObject; use DateTimeInterface; use Doctrine\Common\Collections\Collection; use Doctrine\Persistence\Proxy; use stdClass; use function array_keys; use function count; use function end; use function explode; use function extension_loaded; use function get_class; use function html_entity_decode; use function ini_get; use function ini_set; use function is_array; use function is_object; use function method_exists; use function ob_end_clean; use function ob_get_contents; use function ob_start; use function spl_object_hash; use function strip_tags; use function var_dump; /** * Static class containing most used debug methods. * * @deprecated The Debug class is deprecated, please use symfony/var-dumper instead. * * @link www.doctrine-project.org */ final class Debug { /** * Private constructor (prevents instantiation). */ private function __construct() { } /** * Prints a dump of the public, protected and private properties of $var. * * @link https://xdebug.org/ * * @param mixed $var The variable to dump. * @param int $maxDepth The maximum nesting level for object properties. * @param bool $stripTags Whether output should strip HTML tags. * @param bool $echo Send the dumped value to the output buffer * * @return string */ public static function dump($var, $maxDepth = 2, $stripTags = true, $echo = true) { $html = ini_get('html_errors'); if ($html !== true) { ini_set('html_errors', 'on'); } if (extension_loaded('xdebug')) { ini_set('xdebug.var_display_max_depth', $maxDepth); } $var = self::export($var, $maxDepth); ob_start(); var_dump($var); $dump = ob_get_contents(); ob_end_clean(); $dumpText = ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump); ini_set('html_errors', $html); if ($echo) { echo $dumpText; } return $dumpText; } /** * @param mixed $var * @param int $maxDepth * * @return mixed */ public static function export($var, $maxDepth) { $return = null; $isObj = is_object($var); if ($var instanceof Collection) { $var = $var->toArray(); } if (! $maxDepth) { return is_object($var) ? get_class($var) : (is_array($var) ? 'Array(' . count($var) . ')' : $var); } if (is_array($var)) { $return = []; foreach ($var as $k => $v) { $return[$k] = self::export($v, $maxDepth - 1); } return $return; } if (! $isObj) { return $var; } $return = new stdClass(); if ($var instanceof DateTimeInterface) { $return->__CLASS__ = get_class($var); $return->date = $var->format('c'); $return->timezone = $var->getTimezone()->getName(); return $return; } $return->__CLASS__ = ClassUtils::getClass($var); if ($var instanceof Proxy) { $return->__IS_PROXY__ = true; $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); } if ($var instanceof ArrayObject || $var instanceof ArrayIterator) { $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); } return self::fillReturnWithClassAttributes($var, $return, $maxDepth); } /** * Fill the $return variable with class attributes * Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075} * * @param object $var * @param int $maxDepth * * @return mixed */ private static function fillReturnWithClassAttributes($var, stdClass $return, $maxDepth) { $clone = (array) $var; foreach (array_keys($clone) as $key) { $aux = explode("\0", $key); $name = end($aux); if ($aux[0] === '') { $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private'); } $return->$name = self::export($clone[$key], $maxDepth - 1); } return $return; } /** * Returns a string representation of an object. * * @param object $obj * * @return string */ public static function toString($obj) { return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj); } } common/src/ClassLoader.php 0000644 00000020442 15120025732 0011527 0 ustar 00 <?php namespace Doctrine\Common; use function class_exists; use function interface_exists; use function is_array; use function is_file; use function reset; use function spl_autoload_functions; use function spl_autoload_register; use function spl_autoload_unregister; use function str_replace; use function stream_resolve_include_path; use function strpos; use function trait_exists; use function trigger_error; use const DIRECTORY_SEPARATOR; use const E_USER_DEPRECATED; @trigger_error(ClassLoader::class . ' is deprecated.', E_USER_DEPRECATED); /** * A <tt>ClassLoader</tt> is an autoloader for class files that can be * installed on the SPL autoload stack. It is a class loader that either loads only classes * of a specific namespace or all namespaces and it is suitable for working together * with other autoloaders in the SPL autoload stack. * * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader * relies on the PHP <code>include_path</code>. * * @deprecated The ClassLoader is deprecated and will be removed in version 4.0 of doctrine/common. */ class ClassLoader { /** * PHP file extension. * * @var string */ protected $fileExtension = '.php'; /** * Current namespace. * * @var string|null */ protected $namespace; /** * Current include path. * * @var string|null */ protected $includePath; /** * PHP namespace separator. * * @var string */ protected $namespaceSeparator = '\\'; /** * Creates a new <tt>ClassLoader</tt> that loads classes of the * specified namespace from the specified include path. * * If no include path is given, the ClassLoader relies on the PHP include_path. * If neither a namespace nor an include path is given, the ClassLoader will * be responsible for loading all classes, thereby relying on the PHP include_path. * * @param string|null $ns The namespace of the classes to load. * @param string|null $includePath The base include path to use. */ public function __construct($ns = null, $includePath = null) { $this->namespace = $ns; $this->includePath = $includePath; } /** * Sets the namespace separator used by classes in the namespace of this ClassLoader. * * @param string $sep The separator to use. * * @return void */ public function setNamespaceSeparator($sep) { $this->namespaceSeparator = $sep; } /** * Gets the namespace separator used by classes in the namespace of this ClassLoader. * * @return string */ public function getNamespaceSeparator() { return $this->namespaceSeparator; } /** * Sets the base include path for all class files in the namespace of this ClassLoader. * * @param string|null $includePath * * @return void */ public function setIncludePath($includePath) { $this->includePath = $includePath; } /** * Gets the base include path for all class files in the namespace of this ClassLoader. * * @return string|null */ public function getIncludePath() { return $this->includePath; } /** * Sets the file extension of class files in the namespace of this ClassLoader. * * @param string $fileExtension * * @return void */ public function setFileExtension($fileExtension) { $this->fileExtension = $fileExtension; } /** * Gets the file extension of class files in the namespace of this ClassLoader. * * @return string */ public function getFileExtension() { return $this->fileExtension; } /** * Registers this ClassLoader on the SPL autoload stack. * * @return void */ public function register() { spl_autoload_register([$this, 'loadClass']); } /** * Removes this ClassLoader from the SPL autoload stack. * * @return void */ public function unregister() { spl_autoload_unregister([$this, 'loadClass']); } /** * Loads the given class or interface. * * @param string $className The name of the class to load. * @psalm-param class-string $className * * @return bool TRUE if the class has been successfully loaded, FALSE otherwise. */ public function loadClass($className) { if (self::typeExists($className)) { return true; } if (! $this->canLoadClass($className)) { return false; } require($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; return self::typeExists($className); } /** * Asks this ClassLoader whether it can potentially load the class (file) with * the given name. * * @param string $className The fully-qualified name of the class. * @psalm-param class-string $className * * @return bool TRUE if this ClassLoader can load the class, FALSE otherwise. */ public function canLoadClass($className) { if ($this->namespace !== null && strpos($className, $this->namespace . $this->namespaceSeparator) !== 0) { return false; } $file = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; if ($this->includePath !== null) { return is_file($this->includePath . DIRECTORY_SEPARATOR . $file); } return stream_resolve_include_path($file) !== false; } /** * Checks whether a class with a given name exists. A class "exists" if it is either * already defined in the current request or if there is an autoloader on the SPL * autoload stack that is a) responsible for the class in question and b) is able to * load a class file in which the class definition resides. * * If the class is not already defined, each autoloader in the SPL autoload stack * is asked whether it is able to tell if the class exists. If the autoloader is * a <tt>ClassLoader</tt>, {@link canLoadClass} is used, otherwise the autoload * function of the autoloader is invoked and expected to return a value that * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports * that the class exists, TRUE is returned. * * Note that, depending on what kinds of autoloaders are installed on the SPL * autoload stack, the class (file) might already be loaded as a result of checking * for its existence. This is not the case with a <tt>ClassLoader</tt>, who separates * these responsibilities. * * @param string $className The fully-qualified name of the class. * @psalm-param class-string $className * * @return bool TRUE if the class exists as per the definition given above, FALSE otherwise. */ public static function classExists($className) { return self::typeExists($className, true); } /** * Gets the <tt>ClassLoader</tt> from the SPL autoload stack that is responsible * for (and is able to load) the class with the given name. * * @param string $className The name of the class. * @psalm-param class-string $className * * @return ClassLoader|null The <tt>ClassLoader</tt> for the class or NULL if no such <tt>ClassLoader</tt> exists. */ public static function getClassLoader($className) { foreach (spl_autoload_functions() as $loader) { if (! is_array($loader)) { continue; } $classLoader = reset($loader); if ($classLoader instanceof ClassLoader && $classLoader->canLoadClass($className)) { return $classLoader; } } return null; } /** * Checks whether a given type exists * * @param string $type * @param bool $autoload * * @return bool */ private static function typeExists($type, $autoload = false) { return class_exists($type, $autoload) || interface_exists($type, $autoload) || trait_exists($type, $autoload); } } common/src/CommonException.php 0000644 00000000424 15120025732 0012440 0 ustar 00 <?php namespace Doctrine\Common; use Exception; /** * Base exception class for package Doctrine\Common. * * @deprecated The doctrine/common package is deprecated, please use specific packages and their exceptions instead. */ class CommonException extends Exception { } common/src/Comparable.php 0000644 00000001315 15120025732 0011376 0 ustar 00 <?php namespace Doctrine\Common; /** * Comparable interface that allows to compare two value objects to each other for similarity. * * @link www.doctrine-project.org */ interface Comparable { /** * Compares the current object to the passed $other. * * Returns 0 if they are semantically equal, 1 if the other object * is less than the current one, or -1 if its more than the current one. * * This method should not check for identity using ===, only for semantical equality for example * when two different DateTime instances point to the exact same Date + TZ. * * @param mixed $other * * @return int */ public function compareTo($other); } common/.doctrine-project.json 0000644 00000002515 15120025732 0012260 0 ustar 00 { "active": true, "name": "Common", "slug": "common", "docsSlug": "doctrine-common", "versions": [ { "name": "3.4", "branchName": "3.4.x", "slug": "3.4", "current": true, "aliases": [ "current", "stable" ] }, { "name": "3.3", "branchName": "3.3.x", "slug": "3.3", "maintained": false }, { "name": "3.2", "branchName": "3.2.x", "slug": "3.2", "maintained": false }, { "name": "3.1", "branchName": "3.1.x", "slug": "3.1", "maintained": false }, { "name": "3.0", "branchName": "3.0.x", "slug": "3.0", "maintained": false }, { "name": "2.13", "branchName": "2.13.x", "slug": "2.13", "maintained": false }, { "name": "2.12", "branchName": "2.12.x", "slug": "2.12", "maintained": false }, { "name": "2.11", "branchName": "2.11", "slug": "2.11", "maintained": false } ] } common/LICENSE 0000644 00000002051 15120025732 0007034 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. common/README.md 0000644 00000001120 15120025732 0007302 0 ustar 00 # Doctrine Common [](https://github.com/doctrine/common/actions) [](https://codecov.io/gh/doctrine/common) The Doctrine Common project is a library that provides extensions to core PHP functionality. ## More resources: * [Website](https://www.doctrine-project.org/) * [Documentation](https://www.doctrine-project.org/projects/doctrine-common/en/current/) * [Downloads](https://github.com/doctrine/common/releases) common/UPGRADE_TO_2_1 0000644 00000004121 15120025732 0010104 0 ustar 00 This document details all the possible changes that you should investigate when updating your project from Doctrine Common 2.0.x to 2.1 ## AnnotationReader changes The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); // new code necessary starting here $reader->setIgnoreNotImportedAnnotations(true); $reader->setEnableParsePhpImports(false); $reader = new \Doctrine\Common\Annotations\CachedReader( new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() ); ## Annotation Base class or @Annotation Beginning after 2.1-RC2 you have to either extend ``Doctrine\Common\Annotations\Annotation`` or add @Annotation to your annotations class-level docblock, otherwise the class will simply be ignored. ## Removed methods on AnnotationReader * AnnotationReader::setAutoloadAnnotations() * AnnotationReader::getAutoloadAnnotations() * AnnotationReader::isAutoloadAnnotations() ## AnnotationRegistry Autoloading through the PHP autoloader is removed from the 2.1 AnnotationReader. Instead you have to use the global AnnotationRegistry for loading purposes: \Doctrine\Common\Annotations\AnnotationRegistry::registerFile($fileWithAnnotations); \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace($namespace, $dirs = null); \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespaces($namespaces); \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($callable); The $callable for registering a loader accepts a class as first and only parameter and must try to silently autoload it. On success true has to be returned. The registerAutoloadNamespace function registers a PSR-0 compatible silent autoloader for all classes with the given namespace in the given directories. If null is passed as directory the include path will be used. common/UPGRADE_TO_2_2 0000644 00000004573 15120025732 0010120 0 ustar 00 This document details all the possible changes that you should investigate when updating your project from Doctrine Common 2.1 to 2.2: ## Annotation Changes - AnnotationReader::setIgnoreNotImportedAnnotations has been removed, you need to add ignore annotation names which are supposed to be ignored via AnnotationReader::addGlobalIgnoredName - AnnotationReader::setAutoloadAnnotations was deprecated by the AnnotationRegistry in 2.1 and has been removed in 2.2 - AnnotationReader::setEnableParsePhpImports was added to ease transition to the new annotation mechanism in 2.1 and is removed in 2.2 - AnnotationReader::isParsePhpImportsEnabled is removed (see above) - AnnotationReader::setDefaultAnnotationNamespace was deprecated in favor of explicit configuration in 2.1 and will be removed in 2.2 (for isolated projects where you have full-control over _all_ available annotations, we offer a dedicated reader class ``SimpleAnnotationReader``) - AnnotationReader::setAnnotationCreationFunction was deprecated in 2.1 and will be removed in 2.2. We only offer two creation mechanisms which cannot be changed anymore to allow the same reader instance to work with all annotations regardless of which library they are coming from. - AnnotationReader::setAnnotationNamespaceAlias was deprecated in 2.1 and will be removed in 2.2 (see setDefaultAnnotationNamespace) - If you use a class as annotation which has not the @Annotation marker in it's class block, we will now throw an exception instead of silently ignoring it. You can however still achieve the previous behavior using the @IgnoreAnnotation, or AnnotationReader::addGlobalIgnoredName (the exception message will contain detailed instructions when you run into this problem). ## Cache Changes - Renamed old AbstractCache to CacheProvider - Dropped the support to the following functions of all cache providers: - CacheProvider::deleteByWildcard - CacheProvider::deleteByRegEx - CacheProvider::deleteByPrefix - CacheProvider::deleteBySuffix - CacheProvider::deleteAll will not remove ALL entries, it will only mark them as invalid - CacheProvider::flushAll will remove ALL entries, namespaced or not - Added support to MemcachedCache - Added support to WincacheCache ## ClassLoader Changes - ClassLoader::fileExistsInIncludePath() no longer exists. Use the native stream_resolve_include_path() PHP function common/composer.json 0000644 00000003266 15120025732 0010562 0 ustar 00 { "name": "doctrine/common", "type": "library", "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", "keywords": [ "php", "common", "doctrine" ], "homepage": "https://www.doctrine-project.org/projects/common.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}, {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} ], "require": { "php": "^7.1 || ^8.0", "doctrine/persistence": "^2.0 || ^3.0" }, "require-dev": { "doctrine/collections": "^1", "phpstan/phpstan": "^1.4.1", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", "doctrine/coding-standard": "^9.0 || ^10.0", "squizlabs/php_codesniffer": "^3.0", "symfony/phpunit-bridge": "^6.1", "vimeo/psalm": "^4.4" }, "autoload": { "psr-4": { "Doctrine\\Common\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "composer/package-versions-deprecated": true } } } common/phpstan.neon.dist 0000644 00000006404 15120025732 0011335 0 ustar 00 parameters: phpVersion: 80200 level: 3 paths: - src - tests excludePaths: - tests/Common/Proxy/InvalidReturnTypeClass.php - tests/Common/Proxy/InvalidTypeHintClass.php - tests/Common/Proxy/LazyLoadableObjectWithTypedProperties.php - tests/Common/Proxy/MagicIssetClassWithInteger.php - tests/Common/Proxy/NullableNonOptionalHintClass.php - tests/Common/Proxy/PHP81NeverType.php - tests/Common/Proxy/PHP81IntersectionTypes.php - tests/Common/Proxy/Php8UnionTypes.php - tests/Common/Proxy/Php8StaticType.php - tests/Common/Proxy/ProxyGeneratorTest.php - tests/Common/Proxy/ProxyLogicTypedPropertiesTest.php - tests/Common/Proxy/SerializedClass.php - tests/Common/Proxy/VariadicTypeHintClass.php - tests/Common/Proxy/Php71NullableDefaultedNonOptionalHintClass.php - tests/Common/Proxy/generated ignoreErrors: - '#Access to an undefined property Doctrine\\Common\\Proxy\\Proxy::\$publicField#' - message: '#^Result of method Doctrine\\Tests\\Common\\Proxy\\LazyLoadableObjectWithVoid::(adding|incrementing)AndReturningVoid\(\) \(void\) is used\.$#' path: 'tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' - message: '#^Property Doctrine\\Tests\\Common\\Proxy\\ProxyLogicTest::\$initializerCallbackMock \(callable\(\): mixed&PHPUnit\\Framework\\MockObject\\MockObject\) does not accept PHPUnit\\Framework\\MockObject\\MockObject&stdClass\.$#' path: 'tests/Common/Proxy/ProxyLogicTest.php' - message: '#.*LazyLoadableObject.*#' paths: - 'tests/Common/Proxy/ProxyLogicTest.php' - 'tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' - message: '#^Instantiated class Doctrine\\Tests\\Common\\ProxyProxy\\__CG__\\Doctrine\\Tests\\Common\\Proxy\\.* not found.$#' path: 'tests/Common/Proxy/ProxyLogicTest.php' - message: '#^Instantiated class Doctrine\\Tests\\Common\\ProxyProxy\\__CG__\\Doctrine\\Tests\\Common\\Proxy\\.* not found.$#' path: 'tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' - message: '#^Property Doctrine\\Tests\\Common\\Proxy\\ProxyLogicVoidReturnTypeTest::\$initializerCallbackMock \(callable\(\): mixed&PHPUnit\\Framework\\MockObject\\MockObject\) does not accept PHPUnit\\Framework\\MockObject\\MockObject&stdClass\.$#' path: 'tests/Common/Proxy/ProxyLogicVoidReturnTypeTest.php' - message: '#^Method Doctrine\\Tests\\Common\\Proxy\\MagicIssetClassWithInteger::__isset\(\) should return bool but returns int\.$#' path: 'tests/Common/Proxy/MagicIssetClassWithInteger.php' - message: '#^Access to an undefined property Doctrine\\Tests\\Common\\Proxy\\MagicGetByRefClass\:\:\$nonExisting\.$#' path: 'tests/Common/Proxy/ProxyMagicMethodsTest.php' - message: "#^Class Doctrine\\\\Tests\\\\Common\\\\Proxy\\\\MagicIssetClassWithInteger not found\\.$#" count: 1 path: tests/Common/Proxy/ProxyMagicMethodsTest.php includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon common/psalm.xml 0000644 00000000742 15120025732 0007672 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="8" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" phpVersion="8.1" > <projectFiles> <directory name="src" /> <ignoreFiles> <file name="src/Proxy/ProxyGenerator.php" /> </ignoreFiles> </projectFiles> </psalm> sql-formatter/bin/sql-formatter 0000644 00000001665 15120025732 0012644 0 ustar 00 #!/usr/bin/env php <?php if("cli" !== php_sapi_name()) { echo "<p>Run this PHP script from the command line to see CLI syntax highlighting and formatting. It supports Unix pipes or command line argument style.</p>"; echo "<pre><code>php bin/sql-formatter \"SELECT * FROM MyTable WHERE (id>5 AND \\`name\\` LIKE \\"testing\\");\"</code></pre>"; echo "<pre><code>echo \"SELECT * FROM MyTable WHERE (id>5 AND \\`name\\` LIKE \\"testing\\");\" | php bin/sql-formatter</code></pre>"; exit; } if(isset($argv[1])) { $sql = $argv[1]; } else { $sql = stream_get_contents(fopen('php://stdin', 'r')); } $autoloadFiles = [ __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php' ]; foreach ($autoloadFiles as $autoloadFile) { if (file_exists($autoloadFile)) { require_once $autoloadFile; break; } } echo (new \Doctrine\SqlFormatter\SqlFormatter())->format($sql); sql-formatter/src/CliHighlighter.php 0000644 00000004041 15120025732 0013526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; use function sprintf; use const PHP_EOL; final class CliHighlighter implements Highlighter { public const HIGHLIGHT_FUNCTIONS = 'functions'; /** @var array<string, string> */ private $escapeSequences; /** * @param array<string, string> $escapeSequences */ public function __construct(array $escapeSequences = []) { $this->escapeSequences = $escapeSequences + [ self::HIGHLIGHT_QUOTE => "\x1b[34;1m", self::HIGHLIGHT_BACKTICK_QUOTE => "\x1b[35;1m", self::HIGHLIGHT_RESERVED => "\x1b[37m", self::HIGHLIGHT_BOUNDARY => '', self::HIGHLIGHT_NUMBER => "\x1b[32;1m", self::HIGHLIGHT_WORD => '', self::HIGHLIGHT_ERROR => "\x1b[31;1;7m", self::HIGHLIGHT_COMMENT => "\x1b[30;1m", self::HIGHLIGHT_VARIABLE => "\x1b[36;1m", self::HIGHLIGHT_FUNCTIONS => "\x1b[37m", ]; } public function highlightToken(int $type, string $value): string { if ($type === Token::TOKEN_TYPE_BOUNDARY && ($value === '(' || $value === ')')) { return $value; } $prefix = $this->prefix($type); if ($prefix === null) { return $value; } return $prefix . $value . "\x1b[0m"; } private function prefix(int $type): ?string { if (! isset(self::TOKEN_TYPE_TO_HIGHLIGHT[$type])) { return null; } return $this->escapeSequences[self::TOKEN_TYPE_TO_HIGHLIGHT[$type]]; } public function highlightError(string $value): string { return sprintf( '%s%s%s%s', PHP_EOL, $this->escapeSequences[self::HIGHLIGHT_ERROR], $value, "\x1b[0m" ); } public function highlightErrorMessage(string $value): string { return $this->highlightError($value); } public function output(string $string): string { return $string . "\n"; } } sql-formatter/src/Cursor.php 0000644 00000002203 15120025732 0012113 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; final class Cursor { /** @var int */ private $position = -1; /** @var Token[] */ private $tokens; /** * @param Token[] $tokens */ public function __construct(array $tokens) { $this->tokens = $tokens; } public function next(?int $exceptTokenType = null): ?Token { while ($token = $this->tokens[++$this->position] ?? null) { if ($exceptTokenType !== null && $token->isOfType($exceptTokenType)) { continue; } return $token; } return null; } public function previous(?int $exceptTokenType = null): ?Token { while ($token = $this->tokens[--$this->position] ?? null) { if ($exceptTokenType !== null && $token->isOfType($exceptTokenType)) { continue; } return $token; } return null; } public function subCursor(): self { $cursor = new self($this->tokens); $cursor->position = $this->position; return $cursor; } } sql-formatter/src/Highlighter.php 0000644 00000003653 15120025732 0013106 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; interface Highlighter { public const TOKEN_TYPE_TO_HIGHLIGHT = [ Token::TOKEN_TYPE_BOUNDARY => self::HIGHLIGHT_BOUNDARY, Token::TOKEN_TYPE_WORD => self::HIGHLIGHT_WORD, Token::TOKEN_TYPE_BACKTICK_QUOTE => self::HIGHLIGHT_BACKTICK_QUOTE, Token::TOKEN_TYPE_QUOTE => self::HIGHLIGHT_QUOTE, Token::TOKEN_TYPE_RESERVED => self::HIGHLIGHT_RESERVED, Token::TOKEN_TYPE_RESERVED_TOPLEVEL => self::HIGHLIGHT_RESERVED, Token::TOKEN_TYPE_RESERVED_NEWLINE => self::HIGHLIGHT_RESERVED, Token::TOKEN_TYPE_NUMBER => self::HIGHLIGHT_NUMBER, Token::TOKEN_TYPE_VARIABLE => self::HIGHLIGHT_VARIABLE, Token::TOKEN_TYPE_COMMENT => self::HIGHLIGHT_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT => self::HIGHLIGHT_COMMENT, ]; public const HIGHLIGHT_BOUNDARY = 'boundary'; public const HIGHLIGHT_WORD = 'word'; public const HIGHLIGHT_BACKTICK_QUOTE = 'backtickQuote'; public const HIGHLIGHT_QUOTE = 'quote'; public const HIGHLIGHT_RESERVED = 'reserved'; public const HIGHLIGHT_NUMBER = 'number'; public const HIGHLIGHT_VARIABLE = 'variable'; public const HIGHLIGHT_COMMENT = 'comment'; public const HIGHLIGHT_ERROR = 'error'; /** * Highlights a token depending on its type. */ public function highlightToken(int $type, string $value): string; /** * Highlights a token which causes an issue */ public function highlightError(string $value): string; /** * Highlights an error message */ public function highlightErrorMessage(string $value): string; /** * Helper function for building string output * * @param string $string The string to be quoted * * @return string The quoted string */ public function output(string $string): string; } sql-formatter/src/HtmlHighlighter.php 0000644 00000005300 15120025732 0013722 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; use function htmlentities; use function sprintf; use function trim; use const ENT_COMPAT; use const ENT_IGNORE; use const PHP_EOL; final class HtmlHighlighter implements Highlighter { public const HIGHLIGHT_PRE = 'pre'; /** * This flag tells us if queries need to be enclosed in <pre> tags * * @var bool */ private $usePre; /** @var array<string, string> */ private $htmlAttributes; /** * @param array<string, string> $htmlAttributes */ public function __construct(array $htmlAttributes = [], bool $usePre = true) { $this->htmlAttributes = $htmlAttributes + [ self::HIGHLIGHT_QUOTE => 'style="color: blue;"', self::HIGHLIGHT_BACKTICK_QUOTE => 'style="color: purple;"', self::HIGHLIGHT_RESERVED => 'style="font-weight:bold;"', self::HIGHLIGHT_BOUNDARY => '', self::HIGHLIGHT_NUMBER => 'style="color: green;"', self::HIGHLIGHT_WORD => 'style="color: #333;"', self::HIGHLIGHT_ERROR => 'style="background-color: red;"', self::HIGHLIGHT_COMMENT => 'style="color: #aaa;"', self::HIGHLIGHT_VARIABLE => 'style="color: orange;"', self::HIGHLIGHT_PRE => 'style="color: black; background-color: white;"', ]; $this->usePre = $usePre; } public function highlightToken(int $type, string $value): string { $value = htmlentities($value, ENT_COMPAT | ENT_IGNORE, 'UTF-8'); if ($type === Token::TOKEN_TYPE_BOUNDARY && ($value === '(' || $value === ')')) { return $value; } $attributes = $this->attributes($type); if ($attributes === null) { return $value; } return '<span ' . $attributes . '>' . $value . '</span>'; } public function attributes(int $type): ?string { if (! isset(self::TOKEN_TYPE_TO_HIGHLIGHT[$type])) { return null; } return $this->htmlAttributes[self::TOKEN_TYPE_TO_HIGHLIGHT[$type]]; } public function highlightError(string $value): string { return sprintf( '%s<span %s>%s</span>', PHP_EOL, $this->htmlAttributes[self::HIGHLIGHT_ERROR], $value ); } public function highlightErrorMessage(string $value): string { return $this->highlightError($value); } public function output(string $string): string { $string = trim($string); if (! $this->usePre) { return $string; } return '<pre ' . $this->htmlAttributes[self::HIGHLIGHT_PRE] . '>' . $string . '</pre>'; } } sql-formatter/src/NullHighlighter.php 0000644 00000001003 15120025732 0013724 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; final class NullHighlighter implements Highlighter { public function highlightToken(int $type, string $value): string { return $value; } public function highlightError(string $value): string { return $value; } public function highlightErrorMessage(string $value): string { return ' ' . $value; } public function output(string $string): string { return $string; } } sql-formatter/src/SqlFormatter.php 0000644 00000036736 15120025732 0013303 0 ustar 00 <?php declare(strict_types=1); /** * SQL Formatter is a collection of utilities for debugging SQL queries. * It includes methods for formatting, syntax highlighting, removing comments, etc. * * @link http://github.com/jdorn/sql-formatter */ namespace Doctrine\SqlFormatter; use function array_search; use function array_shift; use function array_unshift; use function assert; use function current; use function preg_replace; use function reset; use function rtrim; use function str_repeat; use function str_replace; use function strlen; use function trim; use const PHP_SAPI; final class SqlFormatter { /** @var Highlighter */ private $highlighter; /** @var Tokenizer */ private $tokenizer; public function __construct(?Highlighter $highlighter = null) { $this->tokenizer = new Tokenizer(); $this->highlighter = $highlighter ?? (PHP_SAPI === 'cli' ? new CliHighlighter() : new HtmlHighlighter()); } /** * Format the whitespace in a SQL string to make it easier to read. * * @param string $string The SQL string * * @return string The SQL string with HTML styles and formatting wrapped in a <pre> tag */ public function format(string $string, string $indentString = ' '): string { // This variable will be populated with formatted html $return = ''; // Use an actual tab while formatting and then switch out with $indentString at the end $tab = "\t"; $indentLevel = 0; $newline = false; $inlineParentheses = false; $increaseSpecialIndent = false; $increaseBlockIndent = false; $indentTypes = []; $addedNewline = false; $inlineCount = 0; $inlineIndented = false; $clauseLimit = false; // Tokenize String $cursor = $this->tokenizer->tokenize($string); // Format token by token while ($token = $cursor->next(Token::TOKEN_TYPE_WHITESPACE)) { $highlighted = $this->highlighter->highlightToken( $token->type(), $token->value() ); // If we are increasing the special indent level now if ($increaseSpecialIndent) { $indentLevel++; $increaseSpecialIndent = false; array_unshift($indentTypes, 'special'); } // If we are increasing the block indent level now if ($increaseBlockIndent) { $indentLevel++; $increaseBlockIndent = false; array_unshift($indentTypes, 'block'); } // If we need a new line before the token if ($newline) { $return = rtrim($return, ' '); $return .= "\n" . str_repeat($tab, $indentLevel); $newline = false; $addedNewline = true; } else { $addedNewline = false; } // Display comments directly where they appear in the source if ($token->isOfType(Token::TOKEN_TYPE_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT)) { if ($token->isOfType(Token::TOKEN_TYPE_BLOCK_COMMENT)) { $indent = str_repeat($tab, $indentLevel); $return = rtrim($return, " \t"); $return .= "\n" . $indent; $highlighted = str_replace("\n", "\n" . $indent, $highlighted); } $return .= $highlighted; $newline = true; continue; } if ($inlineParentheses) { // End of inline parentheses if ($token->value() === ')') { $return = rtrim($return, ' '); if ($inlineIndented) { array_shift($indentTypes); $indentLevel--; $return = rtrim($return, ' '); $return .= "\n" . str_repeat($tab, $indentLevel); } $inlineParentheses = false; $return .= $highlighted . ' '; continue; } if ($token->value() === ',') { if ($inlineCount >= 30) { $inlineCount = 0; $newline = true; } } $inlineCount += strlen($token->value()); } // Opening parentheses increase the block indent level and start a new line if ($token->value() === '(') { // First check if this should be an inline parentheses block // Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2) // Allow up to 3 non-whitespace tokens inside inline parentheses $length = 0; $subCursor = $cursor->subCursor(); for ($j = 1; $j <= 250; $j++) { // Reached end of string $next = $subCursor->next(Token::TOKEN_TYPE_WHITESPACE); if (! $next) { break; } // Reached closing parentheses, able to inline it if ($next->value() === ')') { $inlineParentheses = true; $inlineCount = 0; $inlineIndented = false; break; } // Reached an invalid token for inline parentheses if ($next->value() === ';' || $next->value() === '(') { break; } // Reached an invalid token type for inline parentheses if ( $next->isOfType( Token::TOKEN_TYPE_RESERVED_TOPLEVEL, Token::TOKEN_TYPE_RESERVED_NEWLINE, Token::TOKEN_TYPE_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT ) ) { break; } $length += strlen($next->value()); } if ($inlineParentheses && $length > 30) { $increaseBlockIndent = true; $inlineIndented = true; $newline = true; } // Take out the preceding space unless there was whitespace there in the original query $prevToken = $cursor->subCursor()->previous(); if ($prevToken && ! $prevToken->isOfType(Token::TOKEN_TYPE_WHITESPACE)) { $return = rtrim($return, ' '); } if (! $inlineParentheses) { $increaseBlockIndent = true; // Add a newline after the parentheses $newline = true; } } elseif ($token->value() === ')') { // Closing parentheses decrease the block indent level // Remove whitespace before the closing parentheses $return = rtrim($return, ' '); $indentLevel--; // Reset indent level while ($j = array_shift($indentTypes)) { if ($j !== 'special') { break; } $indentLevel--; } if ($indentLevel < 0) { // This is an error $indentLevel = 0; $return .= $this->highlighter->highlightError($token->value()); continue; } // Add a newline before the closing parentheses (if not already added) if (! $addedNewline) { $return .= "\n" . str_repeat($tab, $indentLevel); } } elseif ($token->isOfType(Token::TOKEN_TYPE_RESERVED_TOPLEVEL)) { // Top level reserved words start a new line and increase the special indent level $increaseSpecialIndent = true; // If the last indent type was 'special', decrease the special indent for this round reset($indentTypes); if (current($indentTypes) === 'special') { $indentLevel--; array_shift($indentTypes); } // Add a newline after the top level reserved word $newline = true; // Add a newline before the top level reserved word (if not already added) if (! $addedNewline) { $return = rtrim($return, ' '); $return .= "\n" . str_repeat($tab, $indentLevel); } else { // If we already added a newline, redo the indentation since it may be different now $return = rtrim($return, $tab) . str_repeat($tab, $indentLevel); } if ($token->hasExtraWhitespace()) { $highlighted = preg_replace('/\s+/', ' ', $highlighted); } //if SQL 'LIMIT' clause, start variable to reset newline if ($token->value() === 'LIMIT' && ! $inlineParentheses) { $clauseLimit = true; } } elseif ( $clauseLimit && $token->value() !== ',' && ! $token->isOfType(Token::TOKEN_TYPE_NUMBER, Token::TOKEN_TYPE_WHITESPACE) ) { // Checks if we are out of the limit clause $clauseLimit = false; } elseif ($token->value() === ',' && ! $inlineParentheses) { // Commas start a new line (unless within inline parentheses or SQL 'LIMIT' clause) //If the previous TOKEN_VALUE is 'LIMIT', resets new line if ($clauseLimit === true) { $newline = false; $clauseLimit = false; } else { // All other cases of commas $newline = true; } } elseif ($token->isOfType(Token::TOKEN_TYPE_RESERVED_NEWLINE)) { // Newline reserved words start a new line // Add a newline before the reserved word (if not already added) if (! $addedNewline) { $return = rtrim($return, ' '); $return .= "\n" . str_repeat($tab, $indentLevel); } if ($token->hasExtraWhitespace()) { $highlighted = preg_replace('/\s+/', ' ', $highlighted); } } elseif ($token->isOfType(Token::TOKEN_TYPE_BOUNDARY)) { // Multiple boundary characters in a row should not have spaces between them (not including parentheses) $prevNotWhitespaceToken = $cursor->subCursor()->previous(Token::TOKEN_TYPE_WHITESPACE); if ($prevNotWhitespaceToken && $prevNotWhitespaceToken->isOfType(Token::TOKEN_TYPE_BOUNDARY)) { $prevToken = $cursor->subCursor()->previous(); if ($prevToken && ! $prevToken->isOfType(Token::TOKEN_TYPE_WHITESPACE)) { $return = rtrim($return, ' '); } } } // If the token shouldn't have a space before it if ( $token->value() === '.' || $token->value() === ',' || $token->value() === ';' ) { $return = rtrim($return, ' '); } $return .= $highlighted . ' '; // If the token shouldn't have a space after it if ($token->value() === '(' || $token->value() === '.') { $return = rtrim($return, ' '); } // If this is the "-" of a negative number, it shouldn't have a space after it if ($token->value() !== '-') { continue; } $nextNotWhitespace = $cursor->subCursor()->next(Token::TOKEN_TYPE_WHITESPACE); if (! $nextNotWhitespace || ! $nextNotWhitespace->isOfType(Token::TOKEN_TYPE_NUMBER)) { continue; } $prev = $cursor->subCursor()->previous(Token::TOKEN_TYPE_WHITESPACE); if (! $prev) { continue; } if ( $prev->isOfType( Token::TOKEN_TYPE_QUOTE, Token::TOKEN_TYPE_BACKTICK_QUOTE, Token::TOKEN_TYPE_WORD, Token::TOKEN_TYPE_NUMBER ) ) { continue; } $return = rtrim($return, ' '); } // If there are unmatched parentheses if (array_search('block', $indentTypes) !== false) { $return = rtrim($return, ' '); $return .= $this->highlighter->highlightErrorMessage( 'WARNING: unclosed parentheses or section' ); } // Replace tab characters with the configuration tab character $return = trim(str_replace("\t", $indentString, $return)); return $this->highlighter->output($return); } /** * Add syntax highlighting to a SQL string * * @param string $string The SQL string * * @return string The SQL string with HTML styles applied */ public function highlight(string $string): string { $cursor = $this->tokenizer->tokenize($string); $return = ''; while ($token = $cursor->next()) { $return .= $this->highlighter->highlightToken( $token->type(), $token->value() ); } return $this->highlighter->output($return); } /** * Compress a query by collapsing white space and removing comments * * @param string $string The SQL string * * @return string The SQL string without comments */ public function compress(string $string): string { $result = ''; $cursor = $this->tokenizer->tokenize($string); $whitespace = true; while ($token = $cursor->next()) { // Skip comment tokens if ($token->isOfType(Token::TOKEN_TYPE_COMMENT, Token::TOKEN_TYPE_BLOCK_COMMENT)) { continue; } // Remove extra whitespace in reserved words (e.g "OUTER JOIN" becomes "OUTER JOIN") if ( $token->isOfType( Token::TOKEN_TYPE_RESERVED, Token::TOKEN_TYPE_RESERVED_NEWLINE, Token::TOKEN_TYPE_RESERVED_TOPLEVEL ) ) { $newValue = preg_replace('/\s+/', ' ', $token->value()); assert($newValue !== null); $token = $token->withValue($newValue); } if ($token->isOfType(Token::TOKEN_TYPE_WHITESPACE)) { // If the last token was whitespace, don't add another one if ($whitespace) { continue; } $whitespace = true; // Convert all whitespace to a single space $token = $token->withValue(' '); } else { $whitespace = false; } $result .= $token->value(); } return rtrim($result); } } sql-formatter/src/Token.php 0000644 00000003353 15120025732 0011725 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; use function in_array; use function strpos; final class Token { // Constants for token types public const TOKEN_TYPE_WHITESPACE = 0; public const TOKEN_TYPE_WORD = 1; public const TOKEN_TYPE_QUOTE = 2; public const TOKEN_TYPE_BACKTICK_QUOTE = 3; public const TOKEN_TYPE_RESERVED = 4; public const TOKEN_TYPE_RESERVED_TOPLEVEL = 5; public const TOKEN_TYPE_RESERVED_NEWLINE = 6; public const TOKEN_TYPE_BOUNDARY = 7; public const TOKEN_TYPE_COMMENT = 8; public const TOKEN_TYPE_BLOCK_COMMENT = 9; public const TOKEN_TYPE_NUMBER = 10; public const TOKEN_TYPE_ERROR = 11; public const TOKEN_TYPE_VARIABLE = 12; // Constants for different components of a token public const TOKEN_TYPE = 0; public const TOKEN_VALUE = 1; /** @var int */ private $type; /** @var string */ private $value; public function __construct(int $type, string $value) { $this->type = $type; $this->value = $value; } public function value(): string { return $this->value; } public function type(): int { return $this->type; } public function isOfType(int ...$types): bool { return in_array($this->type, $types, true); } public function hasExtraWhitespace(): bool { return strpos($this->value(), ' ') !== false || strpos($this->value(), "\n") !== false || strpos($this->value(), "\t") !== false; } public function withValue(string $value): self { return new self($this->type(), $value); } } sql-formatter/src/Tokenizer.php 0000644 00000055770 15120025732 0012631 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\SqlFormatter; use function array_combine; use function array_keys; use function array_map; use function arsort; use function assert; use function implode; use function preg_match; use function preg_quote; use function str_replace; use function strlen; use function strpos; use function strtoupper; use function substr; /** * @internal */ final class Tokenizer { /** * Reserved words (for syntax highlighting) * * @var string[] */ private $reserved = [ 'ACCESSIBLE', 'ACTION', 'AFTER', 'AGAINST', 'AGGREGATE', 'ALGORITHM', 'ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AS', 'ASC', 'AUTOCOMMIT', 'AUTO_INCREMENT', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINLOG', 'BOTH', 'CASCADE', 'CASE', 'CHANGE', 'CHANGED', 'CHARACTER SET', 'CHARSET', 'CHECK', 'CHECKSUM', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPRESSED', 'CONCURRENT', 'CONSTRAINT', 'CONTAINS', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT ROW', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY', 'DAY_HOUR', 'DAY_MINUTE', 'DAY_SECOND', 'DEFAULT', 'DEFINER', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DO', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'ELSE', 'ENCLOSED', 'END', 'ENGINE', 'ENGINE_TYPE', 'ENGINES', 'ESCAPE', 'ESCAPED', 'EVENTS', 'EXEC', 'EXECUTE', 'EXISTS', 'EXPLAIN', 'EXTENDED', 'FAST', 'FIELDS', 'FILE', 'FILTER', 'FIRST', 'FIXED', 'FLUSH', 'FOR', 'FORCE', 'FOLLOWING', 'FOREIGN', 'FULL', 'FULLTEXT', 'FUNCTION', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP', 'GROUPS', 'HEAP', 'HIGH_PRIORITY', 'HOSTS', 'HOUR', 'HOUR_MINUTE', 'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IFNULL', 'IGNORE', 'IN', 'INDEX', 'INDEXES', 'INFILE', 'INSERT', 'INSERT_ID', 'INSERT_METHOD', 'INTERVAL', 'INTO', 'INVOKER', 'IS', 'ISOLATION', 'KEY', 'KEYS', 'KILL', 'LAST_INSERT_ID', 'LEADING', 'LEVEL', 'LIKE', 'LINEAR', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOCKS', 'LOGS', 'LOW_PRIORITY', 'MARIA', 'MASTER', 'MASTER_CONNECT_RETRY', 'MASTER_HOST', 'MASTER_LOG_FILE', 'MATCH', 'MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR', 'MAX_ROWS', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 'MEDIUM', 'MERGE', 'MINUTE', 'MINUTE_SECOND', 'MIN_ROWS', 'MODE', 'MONTH', 'MRG_MYISAM', 'MYISAM', 'NAMES', 'NATURAL', 'NO OTHERS', 'NOT', 'NOW()', 'NULL', 'OFFSET', 'ON', 'OPEN', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'ON UPDATE', 'ON DELETE', 'OUTFILE', 'OVER', 'PACK_KEYS', 'PAGE', 'PARTIAL', 'PARTITION', 'PARTITIONS', 'PASSWORD', 'PRECEDING', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE', 'PROCESS', 'PROCESSLIST', 'PURGE', 'QUICK', 'RANGE', 'RAID0', 'RAID_CHUNKS', 'RAID_CHUNKSIZE', 'RAID_TYPE', 'READ', 'READ_ONLY', 'READ_WRITE', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELOAD', 'RENAME', 'REPAIR', 'REPEATABLE', 'REPLACE', 'REPLICATION', 'RESET', 'RESTORE', 'RESTRICT', 'RETURN', 'RETURNS', 'REVOKE', 'RLIKE', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SECOND', 'SECURITY', 'SEPARATOR', 'SERIALIZABLE', 'SESSION', 'SHARE', 'SHOW', 'SHUTDOWN', 'SLAVE', 'SONAME', 'SOUNDS', 'SQL', 'SQL_AUTO_IS_NULL', 'SQL_BIG_RESULT', 'SQL_BIG_SELECTS', 'SQL_BIG_TABLES', 'SQL_BUFFER_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_LOG_BIN', 'SQL_LOG_OFF', 'SQL_LOG_UPDATE', 'SQL_LOW_PRIORITY_UPDATES', 'SQL_MAX_JOIN_SIZE', 'SQL_QUOTE_SHOW_CREATE', 'SQL_SAFE_UPDATES', 'SQL_SELECT_LIMIT', 'SQL_SLAVE_SKIP_COUNTER', 'SQL_SMALL_RESULT', 'SQL_WARNINGS', 'SQL_CACHE', 'SQL_NO_CACHE', 'START', 'STARTING', 'STATUS', 'STOP', 'STORAGE', 'STRAIGHT_JOIN', 'STRING', 'STRIPED', 'SUPER', 'TABLE', 'TABLES', 'TEMPORARY', 'TERMINATED', 'THEN', 'TIES', 'TO', 'TRAILING', 'TRANSACTIONAL', 'TRUE', 'TRUNCATE', 'TYPE', 'TYPES', 'UNBOUNDED', 'UNCOMMITTED', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'USAGE', 'USE', 'USING', 'VARIABLES', 'VIEW', 'WHEN', 'WITH', 'WORK', 'WRITE', 'YEAR_MONTH', ]; /** * For SQL formatting * These keywords will all be on their own line * * @var string[] */ private $reservedToplevel = [ 'WITH', 'SELECT', 'FROM', 'WHERE', 'SET', 'ORDER BY', 'GROUP BY', 'LIMIT', 'DROP', 'VALUES', 'UPDATE', 'HAVING', 'ADD', 'CHANGE', 'MODIFY', 'ALTER TABLE', 'DELETE FROM', 'UNION ALL', 'UNION', 'EXCEPT', 'INTERSECT', 'PARTITION BY', 'ROWS', 'RANGE', 'GROUPS', 'WINDOW', ]; /** @var string[] */ private $reservedNewline = [ 'LEFT OUTER JOIN', 'RIGHT OUTER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'OUTER JOIN', 'INNER JOIN', 'JOIN', 'XOR', 'OR', 'AND', 'EXCLUDE', ]; /** @var string[] */ private $functions = [ 'ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_DECRYPT', 'AES_ENCRYPT', 'APPROX_COUNT_DISTINCT', 'AREA', 'ASBINARY', 'ASCII', 'ASIN', 'ASTEXT', 'ATAN', 'ATAN2', 'AVG', 'BDMPOLYFROMTEXT', 'BDMPOLYFROMWKB', 'BDPOLYFROMTEXT', 'BDPOLYFROMWKB', 'BENCHMARK', 'BIN', 'BIT_AND', 'BIT_COUNT', 'BIT_LENGTH', 'BIT_OR', 'BIT_XOR', 'BOUNDARY', 'BUFFER', 'CAST', 'CEIL', 'CEILING', 'CENTROID', 'CHAR', 'CHARACTER_LENGTH', 'CHARSET', 'CHAR_LENGTH', 'CHECKSUM_AGG', 'COALESCE', 'COERCIBILITY', 'COLLATION', 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONNECTION_ID', 'CONTAINS', 'CONV', 'CONVERT', 'CONVERT_TZ', 'CONVEXHULL', 'COS', 'COT', 'COUNT', 'COUNT_BIG', 'CRC32', 'CROSSES', 'CUME_DIST', 'CURDATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURTIME', 'DATABASE', 'DATE', 'DATEDIFF', 'DATE_ADD', 'DATE_DIFF', 'DATE_FORMAT', 'DATE_SUB', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'DECODE', 'DEFAULT', 'DEGREES', 'DENSE_RANK', 'DES_DECRYPT', 'DES_ENCRYPT', 'DIFFERENCE', 'DIMENSION', 'DISJOINT', 'DISTANCE', 'ELT', 'ENCODE', 'ENCRYPT', 'ENDPOINT', 'ENVELOPE', 'EQUALS', 'EXP', 'EXPORT_SET', 'EXTERIORRING', 'EXTRACT', 'EXTRACTVALUE', 'FIELD', 'FIND_IN_SET', 'FIRST_VALUE', 'FLOOR', 'FORMAT', 'FOUND_ROWS', 'FROM_DAYS', 'FROM_UNIXTIME', 'GEOMCOLLFROMTEXT', 'GEOMCOLLFROMWKB', 'GEOMETRYCOLLECTION', 'GEOMETRYCOLLECTIONFROMTEXT', 'GEOMETRYCOLLECTIONFROMWKB', 'GEOMETRYFROMTEXT', 'GEOMETRYFROMWKB', 'GEOMETRYN', 'GEOMETRYTYPE', 'GEOMFROMTEXT', 'GEOMFROMWKB', 'GET_FORMAT', 'GET_LOCK', 'GLENGTH', 'GREATEST', 'GROUPING', 'GROUPING_ID', 'GROUP_CONCAT', 'GROUP_UNIQUE_USERS', 'HEX', 'HOUR', 'IF', 'IFNULL', 'INET_ATON', 'INET_NTOA', 'INSERT', 'INSTR', 'INTERIORRINGN', 'INTERSECTION', 'INTERSECTS', 'INTERVAL', 'ISCLOSED', 'ISEMPTY', 'ISNULL', 'ISRING', 'ISSIMPLE', 'IS_FREE_LOCK', 'IS_USED_LOCK', 'LAG', 'LAST_DAY', 'LAST_INSERT_ID', 'LAST_VALUE', 'LCASE', 'LEAD', 'LEAST', 'LEFT', 'LENGTH', 'LINEFROMTEXT', 'LINEFROMWKB', 'LINESTRING', 'LINESTRINGFROMTEXT', 'LINESTRINGFROMWKB', 'LISTAGG', 'LN', 'LOAD_FILE', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATE', 'LOG', 'LOG10', 'LOG2', 'LOWER', 'LPAD', 'LTRIM', 'MAKEDATE', 'MAKETIME', 'MAKE_SET', 'MASTER_POS_WAIT', 'MAX', 'MBRCONTAINS', 'MBRDISJOINT', 'MBREQUAL', 'MBRINTERSECTS', 'MBROVERLAPS', 'MBRTOUCHES', 'MBRWITHIN', 'MD5', 'MICROSECOND', 'MID', 'MIN', 'MINUTE', 'MLINEFROMTEXT', 'MLINEFROMWKB', 'MOD', 'MONTH', 'MONTHNAME', 'MPOINTFROMTEXT', 'MPOINTFROMWKB', 'MPOLYFROMTEXT', 'MPOLYFROMWKB', 'MULTILINESTRING', 'MULTILINESTRINGFROMTEXT', 'MULTILINESTRINGFROMWKB', 'MULTIPOINT', 'MULTIPOINTFROMTEXT', 'MULTIPOINTFROMWKB', 'MULTIPOLYGON', 'MULTIPOLYGONFROMTEXT', 'MULTIPOLYGONFROMWKB', 'NAME_CONST', 'NTH_VALUE', 'NTILE', 'NULLIF', 'NUMGEOMETRIES', 'NUMINTERIORRINGS', 'NUMPOINTS', 'OCT', 'OCTET_LENGTH', 'OLD_PASSWORD', 'ORD', 'OVERLAPS', 'PASSWORD', 'PERCENT_RANK', 'PERCENTILE_CONT', 'PERCENTILE_DISC', 'PERIOD_ADD', 'PERIOD_DIFF', 'PI', 'POINT', 'POINTFROMTEXT', 'POINTFROMWKB', 'POINTN', 'POINTONSURFACE', 'POLYFROMTEXT', 'POLYFROMWKB', 'POLYGON', 'POLYGONFROMTEXT', 'POLYGONFROMWKB', 'POSITION', 'POW', 'POWER', 'QUARTER', 'QUOTE', 'RADIANS', 'RAND', 'RANK', 'RELATED', 'RELEASE_LOCK', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'ROUND', 'ROW_COUNT', 'ROW_NUMBER', 'RPAD', 'RTRIM', 'SCHEMA', 'SECOND', 'SEC_TO_TIME', 'SESSION_USER', 'SHA', 'SHA1', 'SIGN', 'SIN', 'SLEEP', 'SOUNDEX', 'SPACE', 'SQRT', 'SRID', 'STARTPOINT', 'STD', 'STDEV', 'STDEVP', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 'STRING_AGG', 'STRCMP', 'STR_TO_DATE', 'SUBDATE', 'SUBSTR', 'SUBSTRING', 'SUBSTRING_INDEX', 'SUBTIME', 'SUM', 'SYMDIFFERENCE', 'SYSDATE', 'SYSTEM_USER', 'TAN', 'TIME', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 'TIME_TO_SEC', 'TOUCHES', 'TO_DAYS', 'TRIM', 'TRUNCATE', 'UCASE', 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UNIQUE_USERS', 'UNIX_TIMESTAMP', 'UPDATEXML', 'UPPER', 'USER', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'UUID', 'VAR', 'VARIANCE', 'VARP', 'VAR_POP', 'VAR_SAMP', 'VERSION', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'WITHIN', 'X', 'Y', 'YEAR', 'YEARWEEK', ]; // Regular expressions for tokenizing /** @var string */ private $regexBoundaries; /** @var string */ private $regexReserved; /** @var string */ private $regexReservedNewline; /** @var string */ private $regexReservedToplevel; /** @var string */ private $regexFunction; /** * Punctuation that can be used as a boundary between other tokens * * @var string[] */ private $boundaries = [ ',', ';', ':', ')', '(', '.', '=', '<', '>', '+', '-', '*', '/', '!', '^', '%', '|', '&', '#', ]; /** * Stuff that only needs to be done once. Builds regular expressions and * sorts the reserved words. */ public function __construct() { // Sort reserved word list from longest word to shortest, 3x faster than usort $reservedMap = array_combine($this->reserved, array_map('strlen', $this->reserved)); assert($reservedMap !== false); arsort($reservedMap); $this->reserved = array_keys($reservedMap); // Set up regular expressions $this->regexBoundaries = '(' . implode( '|', $this->quoteRegex($this->boundaries) ) . ')'; $this->regexReserved = '(' . implode( '|', $this->quoteRegex($this->reserved) ) . ')'; $this->regexReservedToplevel = str_replace(' ', '\\s+', '(' . implode( '|', $this->quoteRegex($this->reservedToplevel) ) . ')'); $this->regexReservedNewline = str_replace(' ', '\\s+', '(' . implode( '|', $this->quoteRegex($this->reservedNewline) ) . ')'); $this->regexFunction = '(' . implode('|', $this->quoteRegex($this->functions)) . ')'; } /** * Takes a SQL string and breaks it into tokens. * Each token is an associative array with type and value. * * @param string $string The SQL string */ public function tokenize(string $string): Cursor { $tokens = []; // Used to make sure the string keeps shrinking on each iteration $oldStringLen = strlen($string) + 1; $token = null; $currentLength = strlen($string); // Keep processing the string until it is empty while ($currentLength) { // If the string stopped shrinking, there was a problem if ($oldStringLen <= $currentLength) { $tokens[] = new Token(Token::TOKEN_TYPE_ERROR, $string); return new Cursor($tokens); } $oldStringLen = $currentLength; // Get the next token and the token type $token = $this->createNextToken($string, $token); $tokenLength = strlen($token->value()); $tokens[] = $token; // Advance the string $string = substr($string, $tokenLength); $currentLength -= $tokenLength; } return new Cursor($tokens); } /** * Return the next token and token type in a SQL string. * Quoted strings, comments, reserved words, whitespace, and punctuation * are all their own tokens. * * @param string $string The SQL string * @param Token|null $previous The result of the previous createNextToken() call * * @return Token An associative array containing the type and value of the token. */ private function createNextToken(string $string, ?Token $previous = null): Token { $matches = []; // Whitespace if (preg_match('/^\s+/', $string, $matches)) { return new Token(Token::TOKEN_TYPE_WHITESPACE, $matches[0]); } // Comment if ( $string[0] === '#' || (isset($string[1]) && ($string[0] === '-' && $string[1] === '-') || (isset($string[1]) && $string[0] === '/' && $string[1] === '*')) ) { // Comment until end of line if ($string[0] === '-' || $string[0] === '#') { $last = strpos($string, "\n"); $type = Token::TOKEN_TYPE_COMMENT; } else { // Comment until closing comment tag $pos = strpos($string, '*/', 2); assert($pos !== false); $last = $pos + 2; $type = Token::TOKEN_TYPE_BLOCK_COMMENT; } if ($last === false) { $last = strlen($string); } return new Token($type, substr($string, 0, $last)); } // Quoted String if ($string[0] === '"' || $string[0] === '\'' || $string[0] === '`' || $string[0] === '[') { return new Token( ($string[0] === '`' || $string[0] === '[' ? Token::TOKEN_TYPE_BACKTICK_QUOTE : Token::TOKEN_TYPE_QUOTE), $this->getQuotedString($string) ); } // User-defined Variable if (($string[0] === '@' || $string[0] === ':') && isset($string[1])) { $value = null; $type = Token::TOKEN_TYPE_VARIABLE; // If the variable name is quoted if ($string[1] === '"' || $string[1] === '\'' || $string[1] === '`') { $value = $string[0] . $this->getQuotedString(substr($string, 1)); } else { // Non-quoted variable name preg_match('/^(' . $string[0] . '[a-zA-Z0-9\._\$]+)/', $string, $matches); if ($matches) { $value = $matches[1]; } } if ($value !== null) { return new Token($type, $value); } } // Number (decimal, binary, or hex) if ( preg_match( '/^([0-9]+(\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)($|\s|"\'`|' . $this->regexBoundaries . ')/', $string, $matches ) ) { return new Token(Token::TOKEN_TYPE_NUMBER, $matches[1]); } // Boundary Character (punctuation and symbols) if (preg_match('/^(' . $this->regexBoundaries . ')/', $string, $matches)) { return new Token(Token::TOKEN_TYPE_BOUNDARY, $matches[1]); } // A reserved word cannot be preceded by a '.' // this makes it so in "mytable.from", "from" is not considered a reserved word if (! $previous || $previous->value() !== '.') { $upper = strtoupper($string); // Top Level Reserved Word if ( preg_match( '/^(' . $this->regexReservedToplevel . ')($|\s|' . $this->regexBoundaries . ')/', $upper, $matches ) ) { return new Token( Token::TOKEN_TYPE_RESERVED_TOPLEVEL, substr($string, 0, strlen($matches[1])) ); } // Newline Reserved Word if ( preg_match( '/^(' . $this->regexReservedNewline . ')($|\s|' . $this->regexBoundaries . ')/', $upper, $matches ) ) { return new Token( Token::TOKEN_TYPE_RESERVED_NEWLINE, substr($string, 0, strlen($matches[1])) ); } // Other Reserved Word if ( preg_match( '/^(' . $this->regexReserved . ')($|\s|' . $this->regexBoundaries . ')/', $upper, $matches ) ) { return new Token( Token::TOKEN_TYPE_RESERVED, substr($string, 0, strlen($matches[1])) ); } } // A function must be succeeded by '(' // this makes it so "count(" is considered a function, but "count" alone is not $upper = strtoupper($string); // function if (preg_match('/^(' . $this->regexFunction . '[(]|\s|[)])/', $upper, $matches)) { return new Token( Token::TOKEN_TYPE_RESERVED, substr($string, 0, strlen($matches[1]) - 1) ); } // Non reserved word preg_match('/^(.*?)($|\s|["\'`]|' . $this->regexBoundaries . ')/', $string, $matches); return new Token(Token::TOKEN_TYPE_WORD, $matches[1]); } /** * Helper function for building regular expressions for reserved words and boundary characters * * @param string[] $strings The strings to be quoted * * @return string[] The quoted strings */ private function quoteRegex(array $strings): array { return array_map(static function (string $string): string { return preg_quote($string, '/'); }, $strings); } private function getQuotedString(string $string): string { $ret = ''; // This checks for the following patterns: // 1. backtick quoted string using `` to escape // 2. square bracket quoted string (SQL Server) using ]] to escape // 3. double quoted string using "" or \" to escape // 4. single quoted string using '' or \' to escape if ( preg_match( '/^(((`[^`]*($|`))+)| ((\[[^\]]*($|\]))(\][^\]]*($|\]))*)| (("[^"\\\\]*(?:\\\\.[^"\\\\]*)*("|$))+)| ((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+))/sx', $string, $matches ) ) { $ret = $matches[1]; } return $ret; } } sql-formatter/CONTRIBUTING.md 0000644 00000000745 15120025733 0011601 0 ustar 00 # CONTRIBUTING Make sure you read our [contributing guide][contributing guide on the website]. [contributing guide on the website]:https://www.doctrine-project.org/contribute ## Installing dependencies ```shell composer install composer bin all install ``` ## Running checks locally Here is a script to run all checks, you can use it as a git hook: ```shell #!/bin/bash -eu vendor/bin/phpunit --testdox vendor/bin/psalm echo '' | vendor/bin/phpcs vendor/bin/phpstan analyze ``` sql-formatter/LICENSE.txt 0000644 00000002116 15120025733 0011165 0 ustar 00 The MIT License (MIT) Copyright (c) 2013 Jeremy Dorn <jeremy@jeremydorn.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sql-formatter/README.md 0000644 00000006216 15120025733 0010626 0 ustar 00 # SqlFormatter A lightweight php package for formatting sql statements. It can automatically indent and add line breaks in addition to syntax highlighting. ## History This package is a fork from https://github.com/jdorn/sql-formatter Here is what the original History section says: > I found myself having to debug auto-generated SQL statements all the time and > wanted some way to easily output formatted HTML without having to include a > huge library or copy and paste into online formatters. > I was originally planning to extract the formatting code from PhpMyAdmin, > but that was 10,000+ lines of code and used global variables. > I saw that other people had the same problem and used Stack Overflow user > losif's answer as a starting point. http://stackoverflow.com/a/3924147 ― @jdorn ## Usage The `SqlFormatter` class has a method `format` which takes an SQL string as input and returns a formatted block. Sample usage: ```php <?php require_once 'vendor/autoload.php'; use Doctrine\SqlFormatter\SqlFormatter; $query = "SELECT count(*),`Column1`,`Testing`, `Testing Three` FROM `Table1` WHERE Column1 = 'testing' AND ( (`Column2` = `Column3` OR Column4 >= NOW()) ) GROUP BY Column1 ORDER BY Column3 DESC LIMIT 5,10"; echo (new SqlFormatter())->format($query); ``` Output: <img src="examples/readme_format_html.svg" width="600" height="450" alt="formatted output with HTML Highlight"> When you run php under cli and instantiated `SqlFormatter` without argument, highlighted with `CliHighlighter`. SqlFormatter constructor takes `Highlighter` implementations. `HtmlHighlighter` etc. ### Formatting Only If you don't want syntax highlighting and only want the indentations and line breaks, pass in a `NullHighlighter` instance as the second parameter. This is useful for outputting to error logs or other non-html formats. ```php <?php use Doctrine\SqlFormatter\NullHighlighter; use Doctrine\SqlFormatter\SqlFormatter; echo (new SqlFormatter(new NullHighlighter()))->format($query); ``` Output: ``` SELECT count(*), `Column1`, `Testing`, `Testing Three` FROM `Table1` WHERE Column1 = 'testing' AND ( ( `Column2` = `Column3` OR Column4 >= NOW() ) ) GROUP BY Column1 ORDER BY Column3 DESC LIMIT 5, 10 ``` ### Syntax Highlighting Only There is a separate method `highlight` that preserves all original whitespace and just adds syntax highlighting. This is useful for sql that is already well formatted and just needs to be a little easier to read. ```php <?php echo (new SqlFormatter())->highlight($query); ``` Output: <img src="examples/readme_highlight_html.svg" width="800" height="150" alt="HTML Highlight output"> ### Compress Query The `compress` method removes all comments and compresses whitespace. This is useful for outputting queries that can be copy pasted to the command line easily. ```sql -- This is a comment SELECT /* This is another comment On more than one line */ Id #This is one final comment as temp, DateCreated as Created FROM MyTable; ``` ```php echo (new SqlFormatter())->compress($query); ``` Output: ```sql SELECT Id as temp, DateCreated as Created FROM MyTable; ``` sql-formatter/composer.json 0000644 00000001734 15120025733 0012071 0 ustar 00 { "name": "doctrine/sql-formatter", "description": "a PHP SQL highlighting library", "homepage": "https://github.com/doctrine/sql-formatter/", "keywords": ["sql", "highlight"], "license": "MIT", "type": "library", "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4" }, "authors": [ { "name": "Jeremy Dorn", "email": "jeremy@jeremydorn.com", "homepage": "https://jeremydorn.com/" } ], "autoload": { "psr-4": { "Doctrine\\SqlFormatter\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\SqlFormatter\\Tests\\": "tests" } }, "config": { "allow-plugins": { "bamarni/composer-bin-plugin": true }, "sort-packages": true, "platform": { "php": "7.1.0" } }, "bin": ["bin/sql-formatter"] } lexer/src/AbstractLexer.php 0000644 00000016713 15120025733 0011734 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\Lexer; use ReflectionClass; use UnitEnum; use function get_class; use function implode; use function preg_split; use function sprintf; use function substr; use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_NO_EMPTY; use const PREG_SPLIT_OFFSET_CAPTURE; /** * Base class for writing simple lexers, i.e. for creating small DSLs. * * @template T of UnitEnum|string|int * @template V of string|int */ abstract class AbstractLexer { /** * Lexer original input string. * * @var string */ private $input; /** * Array of scanned tokens. * * @var list<Token<T, V>> */ private $tokens = []; /** * Current lexer position in input string. * * @var int */ private $position = 0; /** * Current peek of current lexer position. * * @var int */ private $peek = 0; /** * The next token in the input. * * @var mixed[]|null * @psalm-var Token<T, V>|null */ public $lookahead; /** * The last matched/seen token. * * @var mixed[]|null * @psalm-var Token<T, V>|null */ public $token; /** * Composed regex for input parsing. * * @var string|null */ private $regex; /** * Sets the input data to be tokenized. * * The Lexer is immediately reset and the new input tokenized. * Any unprocessed tokens from any previous input are lost. * * @param string $input The input to be tokenized. * * @return void */ public function setInput($input) { $this->input = $input; $this->tokens = []; $this->reset(); $this->scan($input); } /** * Resets the lexer. * * @return void */ public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } /** * Resets the peek pointer to 0. * * @return void */ public function resetPeek() { $this->peek = 0; } /** * Resets the lexer position on the input to the given position. * * @param int $position Position to place the lexical scanner. * * @return void */ public function resetPosition($position = 0) { $this->position = $position; } /** * Retrieve the original lexer's input until a given position. * * @param int $position * * @return string */ public function getInputUntilPosition($position) { return substr($this->input, 0, $position); } /** * Checks whether a given token matches the current lookahead. * * @param T $type * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextToken($type) { return $this->lookahead !== null && $this->lookahead->isA($type); } /** * Checks whether any of the given tokens matches the current lookahead. * * @param list<T> $types * * @return bool * * @psalm-assert-if-true !=null $this->lookahead */ public function isNextTokenAny(array $types) { return $this->lookahead !== null && $this->lookahead->isA(...$types); } /** * Moves to the next token in the input string. * * @return bool * * @psalm-assert-if-true !null $this->lookahead */ public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = isset($this->tokens[$this->position]) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } /** * Tells the lexer to skip input tokens until it sees a token with the given value. * * @param T $type The token type to skip until. * * @return void */ public function skipUntil($type) { while ($this->lookahead !== null && ! $this->lookahead->isA($type)) { $this->moveNext(); } } /** * Checks if given value is identical to the given token. * * @param string $value * @param int|string $token * * @return bool */ public function isA($value, $token) { return $this->getType($value) === $token; } /** * Moves the lookahead token forward. * * @return mixed[]|null The next token or NULL if there are no more tokens ahead. * @psalm-return Token<T, V>|null */ public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } return null; } /** * Peeks at the next token, returns it and immediately resets the peek. * * @return mixed[]|null The next token or NULL if there are no more tokens ahead. * @psalm-return Token<T, V>|null */ public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } /** * Scans the input string for tokens. * * @param string $input A query string. * * @return void */ protected function scan($input) { if (! isset($this->regex)) { $this->regex = sprintf( '/(%s)|%s/%s', implode(')|(', $this->getCatchablePatterns()), implode('|', $this->getNonCatchablePatterns()), $this->getModifiers() ); } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($this->regex, $input, -1, $flags); if ($matches === false) { // Work around https://bugs.php.net/78122 $matches = [[$input, 0]]; } foreach ($matches as $match) { // Must remain before 'value' assignment since it can change content $firstMatch = $match[0]; $type = $this->getType($firstMatch); $this->tokens[] = new Token( $firstMatch, $type, $match[1] ); } } /** * Gets the literal for a given token. * * @param T $token * * @return int|string */ public function getLiteral($token) { if ($token instanceof UnitEnum) { return get_class($token) . '::' . $token->name; } $className = static::class; $reflClass = new ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } /** * Regex modifiers * * @return string */ protected function getModifiers() { return 'iu'; } /** * Lexical catchable patterns. * * @return string[] */ abstract protected function getCatchablePatterns(); /** * Lexical non-catchable patterns. * * @return string[] */ abstract protected function getNonCatchablePatterns(); /** * Retrieve token type. Also processes the token value if necessary. * * @param string $value * * @return T|null * * @param-out V $value */ abstract protected function getType(&$value); } lexer/src/Token.php 0000644 00000006361 15120025733 0010247 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\Lexer; use ArrayAccess; use Doctrine\Deprecations\Deprecation; use ReturnTypeWillChange; use UnitEnum; use function in_array; /** * @template T of UnitEnum|string|int * @template V of string|int * @implements ArrayAccess<string,mixed> */ final class Token implements ArrayAccess { /** * The string value of the token in the input string * * @readonly * @var V */ public $value; /** * The type of the token (identifier, numeric, string, input parameter, none) * * @readonly * @var T|null */ public $type; /** * The position of the token in the input string * * @readonly * @var int */ public $position; /** * @param V $value * @param T|null $type */ public function __construct($value, $type, int $position) { $this->value = $value; $this->type = $type; $this->position = $position; } /** @param T ...$types */ public function isA(...$types): bool { return in_array($this->type, $types, true); } /** * @deprecated Use the value, type or position property instead * {@inheritDoc} */ public function offsetExists($offset): bool { Deprecation::trigger( 'doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', self::class ); return in_array($offset, ['value', 'type', 'position'], true); } /** * @deprecated Use the value, type or position property instead * {@inheritDoc} * * @param O $offset * * @return mixed * @psalm-return ( * O is 'value' * ? V * : ( * O is 'type' * ? T|null * : ( * O is 'position' * ? int * : mixed * ) * ) * ) * * @template O of array-key */ #[ReturnTypeWillChange] public function offsetGet($offset) { Deprecation::trigger( 'doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead', self::class ); return $this->$offset; } /** * @deprecated no replacement planned * {@inheritDoc} */ public function offsetSet($offset, $value): void { Deprecation::trigger( 'doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Setting %s properties via ArrayAccess is deprecated', self::class ); $this->$offset = $value; } /** * @deprecated no replacement planned * {@inheritDoc} */ public function offsetUnset($offset): void { Deprecation::trigger( 'doctrine/lexer', 'https://github.com/doctrine/lexer/pull/79', 'Setting %s properties via ArrayAccess is deprecated', self::class ); $this->$offset = null; } } lexer/LICENSE 0000644 00000002051 15120025733 0006664 0 ustar 00 Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lexer/README.md 0000644 00000000557 15120025733 0007147 0 ustar 00 # Doctrine Lexer [](https://github.com/doctrine/lexer/actions) Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers. This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL). https://www.doctrine-project.org/projects/lexer.html lexer/UPGRADE.md 0000644 00000001167 15120025733 0007277 0 ustar 00 Note about upgrading: Doctrine uses static and runtime mechanisms to raise awareness about deprecated code. - Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or Static Analysis tools (like Psalm, phpstan) - Use of our low-overhead runtime deprecation API, details: https://github.com/doctrine/deprecations/ # Upgrade to 2.0.0 `AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return instances of `Doctrine\Common\Lexer\Token`, which is an array-like class Using it as an array is deprecated in favor of using properties of that class. Using `count()` on it is deprecated with no replacement. lexer/composer.json 0000644 00000002726 15120025733 0010412 0 ustar 00 { "name": "doctrine/lexer", "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", "license": "MIT", "type": "library", "keywords": [ "php", "parser", "lexer", "annotations", "docblock" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/lexer.html", "require": { "php": "^7.1 || ^8.0", "doctrine/deprecations": "^1.0" }, "require-dev": { "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "psalm/plugin-phpunit": "^0.18.3", "vimeo/psalm": "^4.11 || ^5.0" }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\Common\\Lexer\\": "tests" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true } } instantiator/docs/en/index.rst 0000644 00000003337 15120025733 0012462 0 ustar 00 Introduction ============ This library provides a way of avoiding usage of constructors when instantiating PHP classes. Installation ============ The suggested installation method is via `composer`_: .. code-block:: console $ composer require doctrine/instantiator Usage ===== The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: .. code-block:: php <?php use Doctrine\Instantiator\Instantiator; use App\Entities\User; $instantiator = new Instantiator(); $user = $instantiator->instantiate(User::class); Contributing ============ - Follow the `Doctrine Coding Standard`_ - The project will follow strict `object calisthenics`_ - Any contribution must provide tests for additional introduced conditions - Any un-confirmed issue needs a failing test case before being accepted - Pull requests must be sent from a new hotfix/feature branch, not from ``master``. Testing ======= The PHPUnit version to be used is the one installed as a dev- dependency via composer: .. code-block:: console $ ./vendor/bin/phpunit Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won’t be merged. Credits ======= This library was migrated from `ocramius/instantiator`_, which has been donated to the doctrine organization, and which is now deprecated in favour of this package. .. _composer: https://getcomposer.org/ .. _CONTRIBUTING.md: CONTRIBUTING.md .. _ocramius/instantiator: https://github.com/Ocramius/Instantiator .. _Doctrine Coding Standard: https://github.com/doctrine/coding-standard .. _object calisthenics: http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php instantiator/docs/en/sidebar.rst 0000644 00000000046 15120025733 0012756 0 ustar 00 .. toctree:: :depth: 3 index instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php 0000644 00000000300 15120025733 0022515 0 ustar 00 <?php namespace Doctrine\Instantiator\Exception; use Throwable; /** * Base exception marker interface for the instantiator component */ interface ExceptionInterface extends Throwable { } instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php 0000644 00000002730 15120025733 0023717 0 ustar 00 <?php namespace Doctrine\Instantiator\Exception; use InvalidArgumentException as BaseInvalidArgumentException; use ReflectionClass; use function interface_exists; use function sprintf; use function trait_exists; /** * Exception for invalid arguments provided to the instantiator */ class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface { public static function fromNonExistingClass(string $className): self { if (interface_exists($className)) { return new self(sprintf('The provided type "%s" is an interface, and cannot be instantiated', $className)); } if (trait_exists($className)) { return new self(sprintf('The provided type "%s" is a trait, and cannot be instantiated', $className)); } return new self(sprintf('The provided class "%s" does not exist', $className)); } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromAbstractClass(ReflectionClass $reflectionClass): self { return new self(sprintf( 'The provided class "%s" is abstract, and cannot be instantiated', $reflectionClass->getName() )); } public static function fromEnum(string $className): self { return new self(sprintf( 'The provided class "%s" is an enum, and cannot be instantiated', $className )); } } instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php 0000644 00000003127 15120025733 0023730 0 ustar 00 <?php namespace Doctrine\Instantiator\Exception; use Exception; use ReflectionClass; use UnexpectedValueException as BaseUnexpectedValueException; use function sprintf; /** * Exception for given parameters causing invalid/unexpected state on instantiation */ class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface { /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromSerializationTriggeredException( ReflectionClass $reflectionClass, Exception $exception ): self { return new self( sprintf( 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', $reflectionClass->getName() ), 0, $exception ); } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ public static function fromUncleanUnSerialization( ReflectionClass $reflectionClass, string $errorString, int $errorCode, string $errorFile, int $errorLine ): self { return new self( sprintf( 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' . 'in file "%s" at line "%d"', $reflectionClass->getName(), $errorFile, $errorLine ), 0, new Exception($errorString, $errorCode) ); } } instantiator/src/Doctrine/Instantiator/Instantiator.php 0000644 00000016622 15120025733 0017475 0 ustar 00 <?php namespace Doctrine\Instantiator; use ArrayIterator; use Doctrine\Instantiator\Exception\ExceptionInterface; use Doctrine\Instantiator\Exception\InvalidArgumentException; use Doctrine\Instantiator\Exception\UnexpectedValueException; use Exception; use ReflectionClass; use ReflectionException; use Serializable; use function class_exists; use function enum_exists; use function is_subclass_of; use function restore_error_handler; use function set_error_handler; use function sprintf; use function strlen; use function unserialize; use const PHP_VERSION_ID; final class Instantiator implements InstantiatorInterface { /** * Markers used internally by PHP to define whether {@see \unserialize} should invoke * the method {@see \Serializable::unserialize()} when dealing with classes implementing * the {@see \Serializable} interface. * * @deprecated This constant will be private in 2.0 */ public const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C'; /** @deprecated This constant will be private in 2.0 */ public const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O'; /** * Used to instantiate specific classes, indexed by class name. * * @var callable[] */ private static $cachedInstantiators = []; /** * Array of objects that can directly be cloned, indexed by class name. * * @var object[] */ private static $cachedCloneables = []; /** * @param string $className * @phpstan-param class-string<T> $className * * @return object * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate($className) { if (isset(self::$cachedCloneables[$className])) { /** @phpstan-var T */ $cachedCloneable = self::$cachedCloneables[$className]; return clone $cachedCloneable; } if (isset(self::$cachedInstantiators[$className])) { $factory = self::$cachedInstantiators[$className]; return $factory(); } return $this->buildAndCacheFromFactory($className); } /** * Builds the requested object and caches it in static properties for performance * * @phpstan-param class-string<T> $className * * @return object * @phpstan-return T * * @template T of object */ private function buildAndCacheFromFactory(string $className) { $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); $instance = $factory(); if ($this->isSafeToClone(new ReflectionClass($instance))) { self::$cachedCloneables[$className] = clone $instance; } return $instance; } /** * Builds a callable capable of instantiating the given $className without * invoking its constructor. * * @phpstan-param class-string<T> $className * * @phpstan-return callable(): T * * @throws InvalidArgumentException * @throws UnexpectedValueException * @throws ReflectionException * * @template T of object */ private function buildFactory(string $className): callable { $reflectionClass = $this->getReflectionClass($className); if ($this->isInstantiableViaReflection($reflectionClass)) { return [$reflectionClass, 'newInstanceWithoutConstructor']; } $serializedString = sprintf( '%s:%d:"%s":0:{}', is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, strlen($className), $className ); $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); return static function () use ($serializedString) { return unserialize($serializedString); }; } /** * @phpstan-param class-string<T> $className * * @phpstan-return ReflectionClass<T> * * @throws InvalidArgumentException * @throws ReflectionException * * @template T of object */ private function getReflectionClass(string $className): ReflectionClass { if (! class_exists($className)) { throw InvalidArgumentException::fromNonExistingClass($className); } if (PHP_VERSION_ID >= 80100 && enum_exists($className, false)) { throw InvalidArgumentException::fromEnum($className); } $reflection = new ReflectionClass($className); if ($reflection->isAbstract()) { throw InvalidArgumentException::fromAbstractClass($reflection); } return $reflection; } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString): void { set_error_handler(static function (int $code, string $message, string $file, int $line) use ($reflectionClass, &$error): bool { $error = UnexpectedValueException::fromUncleanUnSerialization( $reflectionClass, $message, $code, $file, $line ); return true; }); try { $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); } finally { restore_error_handler(); } if ($error) { throw $error; } } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @throws UnexpectedValueException * * @template T of object */ private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString): void { try { unserialize($serializedString); } catch (Exception $exception) { throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); } } /** * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function isInstantiableViaReflection(ReflectionClass $reflectionClass): bool { return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); } /** * Verifies whether the given class is to be considered internal * * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function hasInternalAncestors(ReflectionClass $reflectionClass): bool { do { if ($reflectionClass->isInternal()) { return true; } $reflectionClass = $reflectionClass->getParentClass(); } while ($reflectionClass); return false; } /** * Checks if a class is cloneable * * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. * * @phpstan-param ReflectionClass<T> $reflectionClass * * @template T of object */ private function isSafeToClone(ReflectionClass $reflectionClass): bool { return $reflectionClass->isCloneable() && ! $reflectionClass->hasMethod('__clone') && ! $reflectionClass->isSubclassOf(ArrayIterator::class); } } instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php 0000644 00000000775 15120025733 0021320 0 ustar 00 <?php namespace Doctrine\Instantiator; use Doctrine\Instantiator\Exception\ExceptionInterface; /** * Instantiator provides utility methods to build objects without invoking their constructors */ interface InstantiatorInterface { /** * @param string $className * @phpstan-param class-string<T> $className * * @return object * @phpstan-return T * * @throws ExceptionInterface * * @template T of object */ public function instantiate($className); } instantiator/.doctrine-project.json 0000644 00000002021 15120025733 0013500 0 ustar 00 { "active": true, "name": "Instantiator", "slug": "instantiator", "docsSlug": "doctrine-instantiator", "codePath": "/src", "versions": [ { "name": "1.5", "branchName": "1.5.x", "slug": "latest", "upcoming": true }, { "name": "1.4", "branchName": "1.4.x", "slug": "1.4", "aliases": [ "current", "stable" ], "maintained": true, "current": true }, { "name": "1.3", "branchName": "1.3.x", "slug": "1.3", "maintained": false }, { "name": "1.2", "branchName": "1.2.x", "slug": "1.2" }, { "name": "1.1", "branchName": "1.1.x", "slug": "1.1" }, { "name": "1.0", "branchName": "1.0.x", "slug": "1.0" } ] } instantiator/CONTRIBUTING.md 0000644 00000002010 15120025733 0011503 0 ustar 00 # Contributing * Follow the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard) * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) * Any contribution must provide tests for additional introduced conditions * Any un-confirmed issue needs a failing test case before being accepted * Pull requests must be sent from a new hotfix/feature branch, not from `master`. ## Installation To install the project and run the tests, you need to clone it first: ```sh $ git clone git://github.com/doctrine/instantiator.git ``` You will then need to run a composer installation: ```sh $ cd Instantiator $ curl -s https://getcomposer.org/installer | php $ php composer.phar update ``` ## Testing The PHPUnit version to be used is the one installed as a dev- dependency via composer: ```sh $ ./vendor/bin/phpunit ``` Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won't be merged. instantiator/LICENSE 0000644 00000002044 15120025733 0010266 0 ustar 00 Copyright (c) 2014 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. instantiator/README.md 0000644 00000003053 15120025733 0010541 0 ustar 00 # Instantiator This library provides a way of avoiding usage of constructors when instantiating PHP classes. [](https://travis-ci.org/doctrine/instantiator) [](https://codecov.io/gh/doctrine/instantiator/branch/master) [](https://www.versioneye.com/package/php--doctrine--instantiator) [](https://packagist.org/packages/doctrine/instantiator) [](https://packagist.org/packages/doctrine/instantiator) ## Installation The suggested installation method is via [composer](https://getcomposer.org/): ```sh composer require doctrine/instantiator ``` ## Usage The instantiator is able to create new instances of any class without using the constructor or any API of the class itself: ```php $instantiator = new \Doctrine\Instantiator\Instantiator(); $instance = $instantiator->instantiate(\My\ClassName\Here::class); ``` ## Contributing Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! ## Credits This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which has been donated to the doctrine organization, and which is now deprecated in favour of this package. instantiator/composer.json 0000644 00000003006 15120025733 0012002 0 ustar 00 { "name": "doctrine/instantiator", "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "type": "library", "license": "MIT", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "instantiate", "constructor" ], "authors": [ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", "homepage": "https://ocramius.github.io/" } ], "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "ext-phar": "*", "ext-pdo": "*", "doctrine/coding-standard": "^9 || ^11", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "vimeo/psalm": "^4.30 || ^5.4" }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "autoload-dev": { "psr-0": { "DoctrineTest\\InstantiatorPerformance\\": "tests", "DoctrineTest\\InstantiatorTest\\": "tests", "DoctrineTest\\InstantiatorTestAsset\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } instantiator/psalm.xml 0000644 00000000721 15120025733 0011117 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="7" phpVersion="8.2" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> orm/bin/doctrine 0000644 00000000076 15120025733 0007644 0 ustar 00 #!/usr/bin/env php <?php include(__DIR__ . '/doctrine.php'); orm/bin/doctrine-pear.php 0000644 00000002277 15120025733 0011364 0 ustar 00 <?php fwrite( STDERR, '[Warning] The use of this script is discouraged. See' . ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console' . ' for instructions on bootstrapping the console runner.' . PHP_EOL ); echo PHP_EOL . PHP_EOL; require_once 'Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine'); $classLoader->register(); $classLoader = new \Doctrine\Common\ClassLoader('Symfony'); $classLoader->register(); $configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php'; $helperSet = null; if (file_exists($configFile)) { if ( ! is_readable($configFile)) { trigger_error( 'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR ); } require $configFile; foreach ($GLOBALS as $helperSetCandidate) { if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) { $helperSet = $helperSetCandidate; break; } } } $helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); \Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet); orm/bin/doctrine.bat 0000644 00000000334 15120025733 0010406 0 ustar 00 @echo off if "%PHPBIN%" == "" set PHPBIN=@php_bin@ if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH GOTO RUN :USE_PEAR_PATH set PHPBIN=%PHP_PEAR_PHP_BIN% :RUN "%PHPBIN%" "@bin_dir@\doctrine" %* orm/bin/doctrine.php 0000644 00000002700 15120025733 0010426 0 ustar 00 <?php use Symfony\Component\Console\Helper\HelperSet; use Doctrine\ORM\Tools\Console\ConsoleRunner; fwrite( STDERR, '[Warning] The use of this script is discouraged. See' . ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console' . ' for instructions on bootstrapping the console runner.' . PHP_EOL ); echo PHP_EOL . PHP_EOL; $autoloadFiles = [ __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php' ]; foreach ($autoloadFiles as $autoloadFile) { if (file_exists($autoloadFile)) { require_once $autoloadFile; break; } } $directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config']; $configFile = null; foreach ($directories as $directory) { $configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php'; if (file_exists($configFile)) { break; } } if ( ! file_exists($configFile)) { ConsoleRunner::printCliConfigTemplate(); exit(1); } if ( ! is_readable($configFile)) { echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n"; exit(1); } $commands = []; $helperSet = require $configFile; if ( ! ($helperSet instanceof HelperSet)) { foreach ($GLOBALS as $helperSetCandidate) { if ($helperSetCandidate instanceof HelperSet) { $helperSet = $helperSetCandidate; break; } } } ConsoleRunner::run($helperSet, $commands); orm/lib/Doctrine/ORM/Cache/Exception/CacheException.php 0000644 00000000334 15120025733 0016645 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use Doctrine\ORM\Cache\CacheException as BaseCacheException; /** * Exception for cache. */ class CacheException extends BaseCacheException { } orm/lib/Doctrine/ORM/Cache/Exception/CannotUpdateReadOnlyCollection.php 0000644 00000000644 15120025733 0022026 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use function sprintf; class CannotUpdateReadOnlyCollection extends CacheException { public static function fromEntityAndField(string $sourceEntity, string $fieldName): self { return new self(sprintf( 'Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName )); } } orm/lib/Doctrine/ORM/Cache/Exception/CannotUpdateReadOnlyEntity.php 0000644 00000000500 15120025733 0021176 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use function sprintf; class CannotUpdateReadOnlyEntity extends CacheException { public static function fromEntity(string $entityName): self { return new self(sprintf('Cannot update a readonly entity "%s"', $entityName)); } } orm/lib/Doctrine/ORM/Cache/Exception/FeatureNotImplemented.php 0000644 00000001327 15120025733 0020226 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; class FeatureNotImplemented extends CacheException { public static function scalarResults(): self { return new self('Second level cache does not support scalar results.'); } public static function multipleRootEntities(): self { return new self('Second level cache does not support multiple root entities.'); } public static function nonSelectStatements(): self { return new self('Second-level cache query supports only select statements.'); } public static function partialEntities(): self { return new self('Second level cache does not support partial entities.'); } } orm/lib/Doctrine/ORM/Cache/Exception/InvalidResultCacheDriver.php 0000644 00000000524 15120025733 0020651 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; /** @deprecated */ final class InvalidResultCacheDriver extends CacheException { public static function create(): self { return new self( 'Invalid result cache driver; it must implement Doctrine\\Common\\Cache\\Cache.' ); } } orm/lib/Doctrine/ORM/Cache/Exception/MetadataCacheNotConfigured.php 0000644 00000000406 15120025733 0021116 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; final class MetadataCacheNotConfigured extends CacheException { public static function create(): self { return new self('Class Metadata Cache is not configured.'); } } orm/lib/Doctrine/ORM/Cache/Exception/MetadataCacheUsesNonPersistentCache.php 0000644 00000000640 15120025733 0022747 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use Doctrine\Common\Cache\Cache; use function get_debug_type; final class MetadataCacheUsesNonPersistentCache extends CacheException { public static function fromDriver(Cache $cache): self { return new self( 'Metadata Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.' ); } } orm/lib/Doctrine/ORM/Cache/Exception/NonCacheableEntity.php 0000644 00000000563 15120025733 0017466 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use function sprintf; class NonCacheableEntity extends CacheException { public static function fromEntity(string $entityName): self { return new self(sprintf( 'Entity "%s" not configured as part of the second-level cache.', $entityName )); } } orm/lib/Doctrine/ORM/Cache/Exception/NonCacheableEntityAssociation.php 0000644 00000000676 15120025733 0021670 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use function sprintf; class NonCacheableEntityAssociation extends CacheException { public static function fromEntityAndField(string $entityName, string $field): self { return new self(sprintf( 'Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field )); } } orm/lib/Doctrine/ORM/Cache/Exception/QueryCacheNotConfigured.php 0000644 00000000372 15120025733 0020505 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; final class QueryCacheNotConfigured extends CacheException { public static function create(): self { return new self('Query Cache is not configured.'); } } orm/lib/Doctrine/ORM/Cache/Exception/QueryCacheUsesNonPersistentCache.php 0000644 00000000632 15120025733 0022335 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Exception; use Doctrine\Common\Cache\Cache; use function get_debug_type; final class QueryCacheUsesNonPersistentCache extends CacheException { public static function fromDriver(Cache $cache): self { return new self( 'Query Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.' ); } } orm/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php 0000644 00000006204 15120025733 0015560 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Logging; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\QueryCacheKey; /** * Interface for logging. */ interface CacheLogger { /** * Log an entity put into second level cache. * * @param string $regionName The name of the cache region. * @param EntityCacheKey $key The cache key of the entity. * * @return void */ public function entityCachePut($regionName, EntityCacheKey $key); /** * Log an entity get from second level cache resulted in a hit. * * @param string $regionName The name of the cache region. * @param EntityCacheKey $key The cache key of the entity. * * @return void */ public function entityCacheHit($regionName, EntityCacheKey $key); /** * Log an entity get from second level cache resulted in a miss. * * @param string $regionName The name of the cache region. * @param EntityCacheKey $key The cache key of the entity. * * @return void */ public function entityCacheMiss($regionName, EntityCacheKey $key); /** * Log an entity put into second level cache. * * @param string $regionName The name of the cache region. * @param CollectionCacheKey $key The cache key of the collection. * * @return void */ public function collectionCachePut($regionName, CollectionCacheKey $key); /** * Log an entity get from second level cache resulted in a hit. * * @param string $regionName The name of the cache region. * @param CollectionCacheKey $key The cache key of the collection. * * @return void */ public function collectionCacheHit($regionName, CollectionCacheKey $key); /** * Log an entity get from second level cache resulted in a miss. * * @param string $regionName The name of the cache region. * @param CollectionCacheKey $key The cache key of the collection. * * @return void */ public function collectionCacheMiss($regionName, CollectionCacheKey $key); /** * Log a query put into the query cache. * * @param string $regionName The name of the cache region. * @param QueryCacheKey $key The cache key of the query. * * @return void */ public function queryCachePut($regionName, QueryCacheKey $key); /** * Log a query get from the query cache resulted in a hit. * * @param string $regionName The name of the cache region. * @param QueryCacheKey $key The cache key of the query. * * @return void */ public function queryCacheHit($regionName, QueryCacheKey $key); /** * Log a query get from the query cache resulted in a miss. * * @param string $regionName The name of the cache region. * @param QueryCacheKey $key The cache key of the query. * * @return void */ public function queryCacheMiss($regionName, QueryCacheKey $key); } orm/lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php 0000644 00000005534 15120025733 0016530 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Logging; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\QueryCacheKey; class CacheLoggerChain implements CacheLogger { /** @var array<string, CacheLogger> */ private $loggers = []; /** * @param string $name * * @return void */ public function setLogger($name, CacheLogger $logger) { $this->loggers[$name] = $logger; } /** * @param string $name * * @return CacheLogger|null */ public function getLogger($name) { return $this->loggers[$name] ?? null; } /** @return array<string, CacheLogger> */ public function getLoggers() { return $this->loggers; } /** * {@inheritDoc} */ public function collectionCacheHit($regionName, CollectionCacheKey $key) { foreach ($this->loggers as $logger) { $logger->collectionCacheHit($regionName, $key); } } /** * {@inheritDoc} */ public function collectionCacheMiss($regionName, CollectionCacheKey $key) { foreach ($this->loggers as $logger) { $logger->collectionCacheMiss($regionName, $key); } } /** * {@inheritDoc} */ public function collectionCachePut($regionName, CollectionCacheKey $key) { foreach ($this->loggers as $logger) { $logger->collectionCachePut($regionName, $key); } } /** * {@inheritDoc} */ public function entityCacheHit($regionName, EntityCacheKey $key) { foreach ($this->loggers as $logger) { $logger->entityCacheHit($regionName, $key); } } /** * {@inheritDoc} */ public function entityCacheMiss($regionName, EntityCacheKey $key) { foreach ($this->loggers as $logger) { $logger->entityCacheMiss($regionName, $key); } } /** * {@inheritDoc} */ public function entityCachePut($regionName, EntityCacheKey $key) { foreach ($this->loggers as $logger) { $logger->entityCachePut($regionName, $key); } } /** * {@inheritDoc} */ public function queryCacheHit($regionName, QueryCacheKey $key) { foreach ($this->loggers as $logger) { $logger->queryCacheHit($regionName, $key); } } /** * {@inheritDoc} */ public function queryCacheMiss($regionName, QueryCacheKey $key) { foreach ($this->loggers as $logger) { $logger->queryCacheMiss($regionName, $key); } } /** * {@inheritDoc} */ public function queryCachePut($regionName, QueryCacheKey $key) { foreach ($this->loggers as $logger) { $logger->queryCachePut($regionName, $key); } } } orm/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php 0000644 00000011744 15120025733 0017640 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Logging; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\QueryCacheKey; use function array_sum; /** * Provide basic second level cache statistics. */ class StatisticsCacheLogger implements CacheLogger { /** @var array<string, int> */ private $cacheMissCountMap = []; /** @var array<string, int> */ private $cacheHitCountMap = []; /** @var array<string, int> */ private $cachePutCountMap = []; /** * {@inheritDoc} */ public function collectionCacheMiss($regionName, CollectionCacheKey $key) { $this->cacheMissCountMap[$regionName] = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function collectionCacheHit($regionName, CollectionCacheKey $key) { $this->cacheHitCountMap[$regionName] = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function collectionCachePut($regionName, CollectionCacheKey $key) { $this->cachePutCountMap[$regionName] = ($this->cachePutCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function entityCacheMiss($regionName, EntityCacheKey $key) { $this->cacheMissCountMap[$regionName] = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function entityCacheHit($regionName, EntityCacheKey $key) { $this->cacheHitCountMap[$regionName] = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function entityCachePut($regionName, EntityCacheKey $key) { $this->cachePutCountMap[$regionName] = ($this->cachePutCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function queryCacheHit($regionName, QueryCacheKey $key) { $this->cacheHitCountMap[$regionName] = ($this->cacheHitCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function queryCacheMiss($regionName, QueryCacheKey $key) { $this->cacheMissCountMap[$regionName] = ($this->cacheMissCountMap[$regionName] ?? 0) + 1; } /** * {@inheritDoc} */ public function queryCachePut($regionName, QueryCacheKey $key) { $this->cachePutCountMap[$regionName] = ($this->cachePutCountMap[$regionName] ?? 0) + 1; } /** * Get the number of entries successfully retrieved from cache. * * @param string $regionName The name of the cache region. * * @return int */ public function getRegionHitCount($regionName) { return $this->cacheHitCountMap[$regionName] ?? 0; } /** * Get the number of cached entries *not* found in cache. * * @param string $regionName The name of the cache region. * * @return int */ public function getRegionMissCount($regionName) { return $this->cacheMissCountMap[$regionName] ?? 0; } /** * Get the number of cacheable entries put in cache. * * @param string $regionName The name of the cache region. * * @return int */ public function getRegionPutCount($regionName) { return $this->cachePutCountMap[$regionName] ?? 0; } /** @return array<string, int> */ public function getRegionsMiss() { return $this->cacheMissCountMap; } /** @return array<string, int> */ public function getRegionsHit() { return $this->cacheHitCountMap; } /** @return array<string, int> */ public function getRegionsPut() { return $this->cachePutCountMap; } /** * Clear region statistics * * @param string $regionName The name of the cache region. * * @return void */ public function clearRegionStats($regionName) { $this->cachePutCountMap[$regionName] = 0; $this->cacheHitCountMap[$regionName] = 0; $this->cacheMissCountMap[$regionName] = 0; } /** * Clear all statistics * * @return void */ public function clearStats() { $this->cachePutCountMap = []; $this->cacheHitCountMap = []; $this->cacheMissCountMap = []; } /** * Get the total number of put in cache. * * @return int */ public function getPutCount() { return array_sum($this->cachePutCountMap); } /** * Get the total number of entries successfully retrieved from cache. * * @return int */ public function getHitCount() { return array_sum($this->cacheHitCountMap); } /** * Get the total number of cached entries *not* found in cache. * * @return int */ public function getMissCount() { return array_sum($this->cacheMissCountMap); } } orm/lib/Doctrine/ORM/Cache/Persister/Collection/AbstractCollectionPersister.php 0000644 00000020724 15120025733 0023565 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Collection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Util\ClassUtils; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\CollectionHydrator; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister; use Doctrine\ORM\Cache\Region; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Collection\CollectionPersister; use Doctrine\ORM\UnitOfWork; use function array_values; use function assert; use function count; /** @psalm-import-type AssociationMapping from ClassMetadata */ abstract class AbstractCollectionPersister implements CachedCollectionPersister { /** @var UnitOfWork */ protected $uow; /** @var ClassMetadataFactory */ protected $metadataFactory; /** @var CollectionPersister */ protected $persister; /** @var ClassMetadata */ protected $sourceEntity; /** @var ClassMetadata */ protected $targetEntity; /** @var mixed[] */ protected $association; /** @var mixed[] */ protected $queuedCache = []; /** @var Region */ protected $region; /** @var string */ protected $regionName; /** @var CollectionHydrator */ protected $hydrator; /** @var CacheLogger|null */ protected $cacheLogger; /** * @param CollectionPersister $persister The collection persister that will be cached. * @param Region $region The collection region. * @param EntityManagerInterface $em The entity manager. * @param AssociationMapping $association The association mapping. */ public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association) { $configuration = $em->getConfiguration(); $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); $cacheFactory = $cacheConfig->getCacheFactory(); $this->region = $region; $this->persister = $persister; $this->association = $association; $this->regionName = $region->getName(); $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); $this->cacheLogger = $cacheConfig->getCacheLogger(); $this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association); $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); $this->targetEntity = $em->getClassMetadata($association['targetEntity']); } /** * {@inheritDoc} */ public function getCacheRegion() { return $this->region; } /** * {@inheritDoc} */ public function getSourceEntityMetadata() { return $this->sourceEntity; } /** * {@inheritDoc} */ public function getTargetEntityMetadata() { return $this->targetEntity; } /** * {@inheritDoc} */ public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) { $cache = $this->region->get($key); if ($cache === null) { return null; } return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection); } /** * {@inheritDoc} */ public function storeCollectionCache(CollectionCacheKey $key, $elements) { $associationMapping = $this->sourceEntity->associationMappings[$key->association]; $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); assert($targetPersister instanceof CachedEntityPersister); $targetRegion = $targetPersister->getCacheRegion(); $targetHydrator = $targetPersister->getEntityHydrator(); // Only preserve ordering if association configured it if (! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) { // Elements may be an array or a Collection $elements = array_values($elements instanceof Collection ? $elements->getValues() : $elements); } $entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements); foreach ($entry->identifiers as $index => $entityKey) { if ($targetRegion->contains($entityKey)) { continue; } $class = $this->targetEntity; $className = ClassUtils::getClass($elements[$index]); if ($className !== $this->targetEntity->name) { $class = $this->metadataFactory->getMetadataFor($className); } $entity = $elements[$index]; $entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity); $targetRegion->put($entityKey, $entityEntry); } $cached = $this->region->put($key, $entry); if ($this->cacheLogger && $cached) { $this->cacheLogger->collectionCachePut($this->regionName, $key); } } /** * {@inheritDoc} */ public function contains(PersistentCollection $collection, $element) { return $this->persister->contains($collection, $element); } /** * {@inheritDoc} */ public function containsKey(PersistentCollection $collection, $key) { return $this->persister->containsKey($collection, $key); } /** * {@inheritDoc} */ public function count(PersistentCollection $collection) { $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); $entry = $this->region->get($key); if ($entry !== null) { return count($entry->identifiers); } return $this->persister->count($collection); } /** * {@inheritDoc} */ public function get(PersistentCollection $collection, $index) { return $this->persister->get($collection, $index); } /** * {@inheritDoc} */ public function slice(PersistentCollection $collection, $offset, $length = null) { return $this->persister->slice($collection, $offset, $length); } /** * {@inheritDoc} */ public function loadCriteria(PersistentCollection $collection, Criteria $criteria) { return $this->persister->loadCriteria($collection, $criteria); } /** * Clears cache entries related to the current collection * * @deprecated This method is not used anymore. * * @return void */ protected function evictCollectionCache(PersistentCollection $collection) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9512', 'The method %s() is deprecated and will be removed without replacement.' ); $key = new CollectionCacheKey( $this->sourceEntity->rootEntityName, $this->association['fieldName'], $this->uow->getEntityIdentifier($collection->getOwner()) ); $this->region->evict($key); if ($this->cacheLogger) { $this->cacheLogger->collectionCachePut($this->regionName, $key); } } /** * @deprecated This method is not used anymore. * * @param string $targetEntity * @param object $element * @psalm-param class-string $targetEntity * * @return void */ protected function evictElementCache($targetEntity, $element) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9512', 'The method %s() is deprecated and will be removed without replacement.' ); $targetPersister = $this->uow->getEntityPersister($targetEntity); assert($targetPersister instanceof CachedEntityPersister); $targetRegion = $targetPersister->getCacheRegion(); $key = new EntityCacheKey($targetEntity, $this->uow->getEntityIdentifier($element)); $targetRegion->evict($key); if ($this->cacheLogger) { $this->cacheLogger->entityCachePut($targetRegion->getName(), $key); } } } orm/lib/Doctrine/ORM/Cache/Persister/Collection/CachedCollectionPersister.php 0000644 00000002062 15120025733 0023164 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Collection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Collection\CollectionPersister; /** * Interface for second level cache collection persisters. */ interface CachedCollectionPersister extends CachedPersister, CollectionPersister { /** @return ClassMetadata */ public function getSourceEntityMetadata(); /** @return ClassMetadata */ public function getTargetEntityMetadata(); /** * Loads a collection from cache * * @return mixed[]|null */ public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key); /** * Stores a collection into cache * * @param mixed[]|Collection $elements * * @return void */ public function storeCollectionCache(CollectionCacheKey $key, $elements); } orm/lib/Doctrine/ORM/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php 0000644 00000004511 15120025733 0026640 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Collection; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\PersistentCollection; use function spl_object_id; class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister { /** * {@inheritDoc} */ public function afterTransactionComplete() { if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->storeCollectionCache($item['key'], $item['list']); } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $key) { $this->region->evict($key); } } $this->queuedCache = []; } /** * {@inheritDoc} */ public function afterTransactionRolledBack() { $this->queuedCache = []; } /** * {@inheritDoc} */ public function delete(PersistentCollection $collection) { $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); $this->persister->delete($collection); $this->queuedCache['delete'][spl_object_id($collection)] = $key; } /** * {@inheritDoc} */ public function update(PersistentCollection $collection) { $isInitialized = $collection->isInitialized(); $isDirty = $collection->isDirty(); if (! $isInitialized && ! $isDirty) { return; } $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); // Invalidate non initialized collections OR ordered collection if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) { $this->persister->update($collection); $this->queuedCache['delete'][spl_object_id($collection)] = $key; return; } $this->persister->update($collection); $this->queuedCache['update'][spl_object_id($collection)] = [ 'key' => $key, 'list' => $collection, ]; } } orm/lib/Doctrine/ORM/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php 0000644 00000001354 15120025733 0024625 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Collection; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection; use Doctrine\ORM\PersistentCollection; class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister { /** * {@inheritDoc} */ public function update(PersistentCollection $collection) { if ($collection->isDirty() && $collection->getSnapshot()) { throw CannotUpdateReadOnlyCollection::fromEntityAndField( ClassUtils::getClass($collection->getOwner()), $this->association['fieldName'] ); } parent::update($collection); } } orm/lib/Doctrine/ORM/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php 0000644 00000006242 15120025733 0024777 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Collection; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\ConcurrentRegion; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Collection\CollectionPersister; use function spl_object_id; /** @psalm-import-type AssociationMapping from ClassMetadata */ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister { /** @param AssociationMapping $association The association mapping. */ public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association) { parent::__construct($persister, $region, $em, $association); } /** * {@inheritDoc} */ public function afterTransactionComplete() { if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->region->evict($item['key']); } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $item) { $this->region->evict($item['key']); } } $this->queuedCache = []; } /** * {@inheritDoc} */ public function afterTransactionRolledBack() { if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->region->evict($item['key']); } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $item) { $this->region->evict($item['key']); } } $this->queuedCache = []; } /** * {@inheritDoc} */ public function delete(PersistentCollection $collection) { $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); $lock = $this->region->lock($key); $this->persister->delete($collection); if ($lock === null) { return; } $this->queuedCache['delete'][spl_object_id($collection)] = [ 'key' => $key, 'lock' => $lock, ]; } /** * {@inheritDoc} */ public function update(PersistentCollection $collection) { $isInitialized = $collection->isInitialized(); $isDirty = $collection->isDirty(); if (! $isInitialized && ! $isDirty) { return; } $this->persister->update($collection); $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); $lock = $this->region->lock($key); if ($lock === null) { return; } $this->queuedCache['update'][spl_object_id($collection)] = [ 'key' => $key, 'lock' => $lock, ]; } } orm/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php 0000644 00000044260 15120025733 0022130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\EntityHydrator; use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\TimestampCacheKey; use Doctrine\ORM\Cache\TimestampRegion; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Entity\EntityPersister; use Doctrine\ORM\UnitOfWork; use function assert; use function serialize; use function sha1; abstract class AbstractEntityPersister implements CachedEntityPersister { /** @var UnitOfWork */ protected $uow; /** @var ClassMetadataFactory */ protected $metadataFactory; /** @var EntityPersister */ protected $persister; /** @var ClassMetadata */ protected $class; /** @var mixed[] */ protected $queuedCache = []; /** @var Region */ protected $region; /** @var TimestampRegion */ protected $timestampRegion; /** @var TimestampCacheKey */ protected $timestampKey; /** @var EntityHydrator */ protected $hydrator; /** @var Cache */ protected $cache; /** @var CacheLogger|null */ protected $cacheLogger; /** @var string */ protected $regionName; /** * Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations. * * @var array<string>|null */ protected $joinedAssociations; /** * @param EntityPersister $persister The entity persister to cache. * @param Region $region The entity cache region. * @param EntityManagerInterface $em The entity manager. * @param ClassMetadata $class The entity metadata. */ public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class) { $configuration = $em->getConfiguration(); $cacheConfig = $configuration->getSecondLevelCacheConfiguration(); $cacheFactory = $cacheConfig->getCacheFactory(); $this->class = $class; $this->region = $region; $this->persister = $persister; $this->cache = $em->getCache(); $this->regionName = $region->getName(); $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); $this->cacheLogger = $cacheConfig->getCacheLogger(); $this->timestampRegion = $cacheFactory->getTimestampRegion(); $this->hydrator = $cacheFactory->buildEntityHydrator($em, $class); $this->timestampKey = new TimestampCacheKey($this->class->rootEntityName); } /** * {@inheritDoc} */ public function addInsert($entity) { $this->persister->addInsert($entity); } /** * {@inheritDoc} */ public function getInserts() { return $this->persister->getInserts(); } /** * {@inheritDoc} */ public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null) { return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy); } /** * {@inheritDoc} */ public function getCountSQL($criteria = []) { return $this->persister->getCountSQL($criteria); } /** * {@inheritDoc} */ public function getInsertSQL() { return $this->persister->getInsertSQL(); } /** * {@inheritDoc} */ public function getResultSetMapping() { return $this->persister->getResultSetMapping(); } /** * {@inheritDoc} */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) { return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); } /** * {@inheritDoc} */ public function exists($entity, ?Criteria $extraConditions = null) { if ($extraConditions === null) { $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); if ($this->region->contains($key)) { return true; } } return $this->persister->exists($entity, $extraConditions); } /** * {@inheritDoc} */ public function getCacheRegion() { return $this->region; } /** @return EntityHydrator */ public function getEntityHydrator() { return $this->hydrator; } /** * {@inheritDoc} */ public function storeEntityCache($entity, EntityCacheKey $key) { $class = $this->class; $className = ClassUtils::getClass($entity); if ($className !== $this->class->name) { $class = $this->metadataFactory->getMetadataFor($className); } $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); $cached = $this->region->put($key, $entry); if ($this->cacheLogger && $cached) { $this->cacheLogger->entityCachePut($this->regionName, $key); } return $cached; } /** @param object $entity */ private function storeJoinedAssociations($entity): void { if ($this->joinedAssociations === null) { $associations = []; foreach ($this->class->associationMappings as $name => $assoc) { if ( isset($assoc['cache']) && ($assoc['type'] & ClassMetadata::TO_ONE) && ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide']) ) { $associations[] = $name; } } $this->joinedAssociations = $associations; } foreach ($this->joinedAssociations as $name) { $assoc = $this->class->associationMappings[$name]; $assocEntity = $this->class->getFieldValue($entity, $name); if ($assocEntity === null) { continue; } $assocId = $this->uow->getEntityIdentifier($assocEntity); $assocMetadata = $this->metadataFactory->getMetadataFor($assoc['targetEntity']); $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocPersister->storeEntityCache($assocEntity, $assocKey); } } /** * Generates a string of currently query * * @param string $query * @param string[]|Criteria $criteria * @param string[]|null $orderBy * @param int|null $limit * @param int|null $offset * * @return string */ protected function getHash($query, $criteria, ?array $orderBy = null, $limit = null, $offset = null) { [$params] = $criteria instanceof Criteria ? $this->persister->expandCriteriaParameters($criteria) : $this->persister->expandParameters($criteria); return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset); } /** * {@inheritDoc} */ public function expandParameters($criteria) { return $this->persister->expandParameters($criteria); } /** * {@inheritDoc} */ public function expandCriteriaParameters(Criteria $criteria) { return $this->persister->expandCriteriaParameters($criteria); } /** * {@inheritDoc} */ public function getClassMetadata() { return $this->persister->getClassMetadata(); } /** * {@inheritDoc} */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit); } /** * {@inheritDoc} */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit); } /** * {@inheritDoc} */ public function getOwningTable($fieldName) { return $this->persister->getOwningTable($fieldName); } /** * {@inheritDoc} */ public function executeInserts() { $this->queuedCache['insert'] = $this->persister->getInserts(); return $this->persister->executeInserts(); } /** * {@inheritDoc} */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null) { if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) { return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); } //handle only EntityRepository#findOneBy $query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy); $hash = $this->getHash($query, $criteria, null, null, null); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); $result = $queryCache->get($queryKey, $rsm); if ($result !== null) { if ($this->cacheLogger) { $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $result[0]; } $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); if ($result === null) { return null; } $cached = $queryCache->put($queryKey, $rsm, [$result]); if ($this->cacheLogger) { $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); if ($cached) { $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } return $result; } /** * {@inheritDoc} */ public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null) { $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); $hash = $this->getHash($query, $criteria, null, null, null); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); $result = $queryCache->get($queryKey, $rsm); if ($result !== null) { if ($this->cacheLogger) { $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $result; } $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset); $cached = $queryCache->put($queryKey, $rsm, $result); if ($this->cacheLogger) { if ($result) { $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); } if ($cached) { $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } return $result; } /** * {@inheritDoc} */ public function loadById(array $identifier, $entity = null) { $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); $cacheEntry = $this->region->get($cacheKey); $class = $this->class; if ($cacheEntry !== null) { if ($cacheEntry->class !== $this->class->name) { $class = $this->metadataFactory->getMetadataFor($cacheEntry->class); } $cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity); if ($cachedEntity !== null) { if ($this->cacheLogger) { $this->cacheLogger->entityCacheHit($this->regionName, $cacheKey); } return $cachedEntity; } } $entity = $this->persister->loadById($identifier, $entity); if ($entity === null) { return null; } $class = $this->class; $className = ClassUtils::getClass($entity); if ($className !== $this->class->name) { $class = $this->metadataFactory->getMetadataFor($className); } $cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity); $cached = $this->region->put($cacheKey, $cacheEntry); if ($cached && ($this->joinedAssociations === null || $this->joinedAssociations)) { $this->storeJoinedAssociations($entity); } if ($this->cacheLogger) { if ($cached) { $this->cacheLogger->entityCachePut($this->regionName, $cacheKey); } $this->cacheLogger->entityCacheMiss($this->regionName, $cacheKey); } return $entity; } /** * {@inheritDoc} */ public function count($criteria = []) { return $this->persister->count($criteria); } /** * {@inheritDoc} */ public function loadCriteria(Criteria $criteria) { $orderBy = $criteria->getOrderings(); $limit = $criteria->getMaxResults(); $offset = $criteria->getFirstResult(); $query = $this->persister->getSelectSQL($criteria); $hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset); $rsm = $this->getResultSetMapping(); $queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey); $queryCache = $this->cache->getQueryCache($this->regionName); $cacheResult = $queryCache->get($queryKey, $rsm); if ($cacheResult !== null) { if ($this->cacheLogger) { $this->cacheLogger->queryCacheHit($this->regionName, $queryKey); } return $cacheResult; } $result = $this->persister->loadCriteria($criteria); $cached = $queryCache->put($queryKey, $rsm, $result); if ($this->cacheLogger) { if ($result) { $this->cacheLogger->queryCacheMiss($this->regionName, $queryKey); } if ($cached) { $this->cacheLogger->queryCachePut($this->regionName, $queryKey); } } return $result; } /** * {@inheritDoc} */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection) { $persister = $this->uow->getCollectionPersister($assoc); $hasCache = ($persister instanceof CachedPersister); if (! $hasCache) { return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); } $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = $this->buildCollectionCacheKey($assoc, $ownerId); $list = $persister->loadCollectionCache($collection, $key); if ($list !== null) { if ($this->cacheLogger) { $this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key); } return $list; } $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $collection); $persister->storeCollectionCache($key, $list); if ($this->cacheLogger) { $this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); } return $list; } /** * {@inheritDoc} */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection) { $persister = $this->uow->getCollectionPersister($assoc); $hasCache = ($persister instanceof CachedPersister); if (! $hasCache) { return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); } $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = $this->buildCollectionCacheKey($assoc, $ownerId); $list = $persister->loadCollectionCache($collection, $key); if ($list !== null) { if ($this->cacheLogger) { $this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key); } return $list; } $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $collection); $persister->storeCollectionCache($key, $list); if ($this->cacheLogger) { $this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); } return $list; } /** * {@inheritDoc} */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = []) { return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier); } /** * {@inheritDoc} */ public function lock(array $criteria, $lockMode) { $this->persister->lock($criteria, $lockMode); } /** * {@inheritDoc} */ public function refresh(array $id, $entity, $lockMode = null) { $this->persister->refresh($id, $entity, $lockMode); } /** * @param array<string, mixed> $association * @param array<string, mixed> $ownerId * * @return CollectionCacheKey */ protected function buildCollectionCacheKey(array $association, $ownerId) { $metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']); assert($metadata instanceof ClassMetadata); return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId); } } orm/lib/Doctrine/ORM/Cache/Persister/Entity/CachedEntityPersister.php 0000644 00000001152 15120025733 0021525 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\EntityHydrator; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\Persisters\Entity\EntityPersister; /** * Interface for second level cache entity persisters. */ interface CachedEntityPersister extends CachedPersister, EntityPersister { /** @return EntityHydrator */ public function getEntityHydrator(); /** * @param object $entity * * @return bool */ public function storeEntityCache($entity, EntityCacheKey $key); } orm/lib/Doctrine/ORM/Cache/Persister/Entity/NonStrictReadWriteCachedEntityPersister.php 0000644 00000004750 15120025733 0025207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\ORM\Cache\EntityCacheKey; use function get_class; /** * Specific non-strict read/write cached entity persister */ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister { /** * {@inheritDoc} */ public function afterTransactionComplete() { $isChanged = false; if (isset($this->queuedCache['insert'])) { foreach ($this->queuedCache['insert'] as $entity) { $isChanged = $this->updateCache($entity, $isChanged); } } if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $entity) { $isChanged = $this->updateCache($entity, $isChanged); } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $key) { $this->region->evict($key); $isChanged = true; } } if ($isChanged) { $this->timestampRegion->update($this->timestampKey); } $this->queuedCache = []; } /** * {@inheritDoc} */ public function afterTransactionRolledBack() { $this->queuedCache = []; } /** * {@inheritDoc} */ public function delete($entity) { $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $deleted = $this->persister->delete($entity); if ($deleted) { $this->region->evict($key); } $this->queuedCache['delete'][] = $key; return $deleted; } /** * {@inheritDoc} */ public function update($entity) { $this->persister->update($entity); $this->queuedCache['update'][] = $entity; } /** @param object $entity */ private function updateCache($entity, bool $isChanged): bool { $class = $this->metadataFactory->getMetadataFor(get_class($entity)); $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $entry = $this->hydrator->buildCacheEntry($class, $key, $entity); $cached = $this->region->put($key, $entry); $isChanged = $isChanged || $cached; if ($this->cacheLogger && $cached) { $this->cacheLogger->entityCachePut($this->regionName, $key); } return $isChanged; } } orm/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php 0000644 00000000754 15120025733 0023172 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity; /** * Specific read-only region entity persister */ class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister { /** * {@inheritDoc} */ public function update($entity) { throw CannotUpdateReadOnlyEntity::fromEntity(ClassUtils::getClass($entity)); } } orm/lib/Doctrine/ORM/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php 0000644 00000005515 15120025733 0023343 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister\Entity; use Doctrine\ORM\Cache\ConcurrentRegion; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\Entity\EntityPersister; /** * Specific read-write entity persister */ class ReadWriteCachedEntityPersister extends AbstractEntityPersister { public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class) { parent::__construct($persister, $region, $em, $class); } /** * {@inheritDoc} */ public function afterTransactionComplete() { $isChanged = true; if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->region->evict($item['key']); $isChanged = true; } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $item) { $this->region->evict($item['key']); $isChanged = true; } } if ($isChanged) { $this->timestampRegion->update($this->timestampKey); } $this->queuedCache = []; } /** * {@inheritDoc} */ public function afterTransactionRolledBack() { if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->region->evict($item['key']); } } if (isset($this->queuedCache['delete'])) { foreach ($this->queuedCache['delete'] as $item) { $this->region->evict($item['key']); } } $this->queuedCache = []; } /** * {@inheritDoc} */ public function delete($entity) { $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $lock = $this->region->lock($key); $deleted = $this->persister->delete($entity); if ($deleted) { $this->region->evict($key); } if ($lock === null) { return $deleted; } $this->queuedCache['delete'][] = [ 'lock' => $lock, 'key' => $key, ]; return $deleted; } /** * {@inheritDoc} */ public function update($entity) { $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); $lock = $this->region->lock($key); $this->persister->update($entity); if ($lock === null) { return; } $this->queuedCache['update'][] = [ 'lock' => $lock, 'key' => $key, ]; } } orm/lib/Doctrine/ORM/Cache/Persister/CachedPersister.php 0000644 00000001266 15120025733 0017062 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Persister; use Doctrine\ORM\Cache\Region; /** * Interface for persister that support second level cache. */ interface CachedPersister { /** * Perform whatever processing is encapsulated here after completion of the transaction. * * @return void */ public function afterTransactionComplete(); /** * Perform whatever processing is encapsulated here after completion of the rolled-back. * * @return void */ public function afterTransactionRolledBack(); /** * Gets the The region access. * * @return Region */ public function getCacheRegion(); } orm/lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php 0000644 00000000412 15120025733 0017450 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Region; /** * A cache region that enables the retrieval of multiple elements with one call * * @deprecated Use {@link DefaultRegion} instead. */ class DefaultMultiGetRegion extends DefaultRegion { } orm/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php 0000644 00000012524 15120025733 0016004 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Region; use Closure; use Doctrine\Common\Cache\Cache as LegacyCache; use Doctrine\Common\Cache\CacheProvider; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\CacheEntry; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\ORM\Cache\Lock; use Doctrine\ORM\Cache\Region; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Traversable; use TypeError; use function array_map; use function get_debug_type; use function iterator_to_array; use function sprintf; use function strtr; /** * The simplest cache region compatible with all doctrine-cache drivers. */ class DefaultRegion implements Region { /** @internal since 2.11, this constant will be private in 3.0. */ public const REGION_KEY_SEPARATOR = '_'; private const REGION_PREFIX = 'DC2_REGION_'; /** * @deprecated since 2.11, this property will be removed in 3.0. * * @var LegacyCache */ protected $cache; /** * @internal since 2.11, this property will be private in 3.0. * * @var string */ protected $name; /** * @internal since 2.11, this property will be private in 3.0. * * @var int */ protected $lifetime = 0; /** @var CacheItemPoolInterface */ private $cacheItemPool; /** @param CacheItemPoolInterface $cacheItemPool */ public function __construct(string $name, $cacheItemPool, int $lifetime = 0) { if ($cacheItemPool instanceof LegacyCache) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9322', 'Passing an instance of %s to %s is deprecated, pass a %s instead.', get_debug_type($cacheItemPool), __METHOD__, CacheItemPoolInterface::class ); $this->cache = $cacheItemPool; $this->cacheItemPool = CacheAdapter::wrap($cacheItemPool); } elseif (! $cacheItemPool instanceof CacheItemPoolInterface) { throw new TypeError(sprintf( '%s: Parameter #2 is expected to be an instance of %s, got %s.', __METHOD__, CacheItemPoolInterface::class, get_debug_type($cacheItemPool) )); } else { $this->cache = DoctrineProvider::wrap($cacheItemPool); $this->cacheItemPool = $cacheItemPool; } $this->name = $name; $this->lifetime = $lifetime; } /** * {@inheritDoc} */ public function getName() { return $this->name; } /** * @deprecated * * @return CacheProvider */ public function getCache() { return $this->cache; } /** * {@inheritDoc} */ public function contains(CacheKey $key) { return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key)); } /** * {@inheritDoc} */ public function get(CacheKey $key) { $item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key)); $entry = $item->isHit() ? $item->get() : null; if (! $entry instanceof CacheEntry) { return null; } return $entry; } /** * {@inheritDoc} */ public function getMultiple(CollectionCacheEntry $collection) { $keys = array_map( Closure::fromCallable([$this, 'getCacheEntryKey']), $collection->identifiers ); /** @var iterable<string, CacheItemInterface> $items */ $items = $this->cacheItemPool->getItems($keys); if ($items instanceof Traversable) { $items = iterator_to_array($items); } $result = []; foreach ($keys as $arrayKey => $cacheKey) { if (! isset($items[$cacheKey]) || ! $items[$cacheKey]->isHit()) { return null; } $entry = $items[$cacheKey]->get(); if (! $entry instanceof CacheEntry) { return null; } $result[$arrayKey] = $entry; } return $result; } /** * {@inheritDoc} * * @return bool */ public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null) { $item = $this->cacheItemPool ->getItem($this->getCacheEntryKey($key)) ->set($entry); if ($this->lifetime > 0) { $item->expiresAfter($this->lifetime); } return $this->cacheItemPool->save($item); } /** * {@inheritDoc} * * @return bool */ public function evict(CacheKey $key) { return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key)); } /** * {@inheritDoc} * * @return bool */ public function evictAll() { return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name); } /** * @internal since 2.11, this method will be private in 3.0. * * @return string */ protected function getCacheEntryKey(CacheKey $key) { return self::REGION_PREFIX . $this->name . self::REGION_KEY_SEPARATOR . strtr($key->hash, '{}()/\@:', '________'); } } orm/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php 0000644 00000012341 15120025733 0016105 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\CacheEntry; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\ORM\Cache\ConcurrentRegion; use Doctrine\ORM\Cache\Lock; use Doctrine\ORM\Cache\Region; use InvalidArgumentException; use function array_filter; use function array_map; use function chmod; use function file_get_contents; use function file_put_contents; use function fileatime; use function glob; use function is_dir; use function is_file; use function is_writable; use function mkdir; use function sprintf; use function time; use function unlink; use const DIRECTORY_SEPARATOR; use const LOCK_EX; /** * Very naive concurrent region, based on file locks. */ class FileLockRegion implements ConcurrentRegion { public const LOCK_EXTENSION = 'lock'; /** @var Region */ private $region; /** @var string */ private $directory; /** @psalm-var numeric-string */ private $lockLifetime; /** * @param string $directory * @param numeric-string $lockLifetime * * @throws InvalidArgumentException */ public function __construct(Region $region, $directory, $lockLifetime) { if (! is_dir($directory) && ! @mkdir($directory, 0775, true)) { throw new InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory)); } if (! is_writable($directory)) { throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory)); } $this->region = $region; $this->directory = $directory; $this->lockLifetime = $lockLifetime; } private function isLocked(CacheKey $key, ?Lock $lock = null): bool { $filename = $this->getLockFileName($key); if (! is_file($filename)) { return false; } $time = $this->getLockTime($filename); $content = $this->getLockContent($filename); if (! $content || ! $time) { @unlink($filename); return false; } if ($lock && $content === $lock->value) { return false; } // outdated lock if ($time + $this->lockLifetime <= time()) { @unlink($filename); return false; } return true; } private function getLockFileName(CacheKey $key): string { return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION; } /** @return string|false */ private function getLockContent(string $filename) { return @file_get_contents($filename); } /** @return int|false */ private function getLockTime(string $filename) { return @fileatime($filename); } /** * {@inheritDoc} */ public function getName() { return $this->region->getName(); } /** * {@inheritDoc} */ public function contains(CacheKey $key) { if ($this->isLocked($key)) { return false; } return $this->region->contains($key); } /** * {@inheritDoc} */ public function get(CacheKey $key) { if ($this->isLocked($key)) { return null; } return $this->region->get($key); } /** * {@inheritDoc} */ public function getMultiple(CollectionCacheEntry $collection) { if (array_filter(array_map([$this, 'isLocked'], $collection->identifiers))) { return null; } return $this->region->getMultiple($collection); } /** * {@inheritDoc} */ public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null) { if ($this->isLocked($key, $lock)) { return false; } return $this->region->put($key, $entry); } /** * {@inheritDoc} */ public function evict(CacheKey $key) { if ($this->isLocked($key)) { @unlink($this->getLockFileName($key)); } return $this->region->evict($key); } /** * {@inheritDoc} */ public function evictAll() { // The check below is necessary because on some platforms glob returns false // when nothing matched (even though no errors occurred) $filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)); if ($filenames) { foreach ($filenames as $filename) { @unlink($filename); } } return $this->region->evictAll(); } /** * {@inheritDoc} */ public function lock(CacheKey $key) { if ($this->isLocked($key)) { return null; } $lock = Lock::createLockRead(); $filename = $this->getLockFileName($key); if (! @file_put_contents($filename, $lock->value, LOCK_EX)) { return null; } chmod($filename, 0664); return $lock; } /** * {@inheritDoc} */ public function unlock(CacheKey $key, Lock $lock) { if ($this->isLocked($key, $lock)) { return false; } return @unlink($this->getLockFileName($key)); } } orm/lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php 0000644 00000000755 15120025733 0017311 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\TimestampCacheEntry; use Doctrine\ORM\Cache\TimestampRegion; /** * Tracks the timestamps of the most recent updates to particular keys. */ class UpdateTimestampCache extends DefaultRegion implements TimestampRegion { /** * {@inheritDoc} */ public function update(CacheKey $key) { $this->put($key, new TimestampCacheEntry()); } } orm/lib/Doctrine/ORM/Cache/AssociationCacheEntry.php 0000644 00000002426 15120025733 0016253 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Association cache entry */ class AssociationCacheEntry implements CacheEntry { /** * The entity identifier * * @readonly Public only for performance reasons, it should be considered immutable. * @var array<string, mixed> */ public $identifier; /** * The entity class name * * @readonly Public only for performance reasons, it should be considered immutable. * @var string * @psalm-var class-string */ public $class; /** * @param string $class The entity class. * @param array<string, mixed> $identifier The entity identifier. * @psalm-param class-string $class */ public function __construct($class, array $identifier) { $this->class = $class; $this->identifier = $identifier; } /** * Creates a new AssociationCacheEntry * * This method allow Doctrine\Common\Cache\PhpFileCache compatibility * * @param array<string, mixed> $values array containing property values * * @return AssociationCacheEntry */ public static function __set_state(array $values) { return new self($values['class'], $values['identifier']); } } orm/lib/Doctrine/ORM/Cache/CacheConfiguration.php 0000644 00000003523 15120025733 0015563 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Logging\CacheLogger; /** * Configuration container for second-level cache. */ class CacheConfiguration { /** @var CacheFactory|null */ private $cacheFactory; /** @var RegionsConfiguration|null */ private $regionsConfig; /** @var CacheLogger|null */ private $cacheLogger; /** @var QueryCacheValidator|null */ private $queryValidator; /** @return CacheFactory|null */ public function getCacheFactory() { return $this->cacheFactory; } /** @return void */ public function setCacheFactory(CacheFactory $factory) { $this->cacheFactory = $factory; } /** @return CacheLogger|null */ public function getCacheLogger() { return $this->cacheLogger; } /** @return void */ public function setCacheLogger(CacheLogger $logger) { $this->cacheLogger = $logger; } /** @return RegionsConfiguration */ public function getRegionsConfiguration() { if ($this->regionsConfig === null) { $this->regionsConfig = new RegionsConfiguration(); } return $this->regionsConfig; } /** @return void */ public function setRegionsConfiguration(RegionsConfiguration $regionsConfig) { $this->regionsConfig = $regionsConfig; } /** @return QueryCacheValidator */ public function getQueryValidator() { if ($this->queryValidator === null) { $this->queryValidator = new TimestampQueryCacheValidator( $this->cacheFactory->getTimestampRegion() ); } return $this->queryValidator; } /** @return void */ public function setQueryValidator(QueryCacheValidator $validator) { $this->queryValidator = $validator; } } orm/lib/Doctrine/ORM/Cache/CacheEntry.php 0000644 00000000354 15120025733 0014054 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Cache entry interface * * <b>IMPORTANT NOTE:</b> * * Fields of classes that implement CacheEntry are public for performance reason. */ interface CacheEntry { } orm/lib/Doctrine/ORM/Cache/CacheException.php 0000644 00000002743 15120025733 0014715 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Exception\ORMException; use function sprintf; /** * Exception for cache. */ class CacheException extends ORMException { /** * @param string $sourceEntity * @param string $fieldName * * @return CacheException */ public static function updateReadOnlyCollection($sourceEntity, $fieldName) { return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName)); } /** * @deprecated This method is not used anymore. * * @param string $entityName * * @return CacheException */ public static function updateReadOnlyEntity($entityName) { return new self(sprintf('Cannot update a readonly entity "%s"', $entityName)); } /** * @param string $entityName * * @return CacheException */ public static function nonCacheableEntity($entityName) { return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName)); } /** * @deprecated This method is not used anymore. * * @param string $entityName * @param string $field * * @return CacheException */ public static function nonCacheableEntityAssociation($entityName, $field) { return new self(sprintf('Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field)); } } orm/lib/Doctrine/ORM/Cache/CacheFactory.php 0000644 00000005164 15120025733 0014366 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister; use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\Collection\CollectionPersister; use Doctrine\ORM\Persisters\Entity\EntityPersister; /** * Contract for building second level cache regions components. * * @psalm-import-type AssociationMapping from ClassMetadata */ interface CacheFactory { /** * Build an entity persister for the given entity metadata. * * @param EntityManagerInterface $em The entity manager. * @param EntityPersister $persister The entity persister that will be cached. * @param ClassMetadata $metadata The entity metadata. * * @return CachedEntityPersister */ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata); /** * Build a collection persister for the given relation mapping. * * @param AssociationMapping $mapping The association mapping. * * @return CachedCollectionPersister */ public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping); /** * Build a query cache based on the given region name * * @param string|null $regionName The region name. * * @return QueryCache The built query cache. */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null); /** * Build an entity hydrator * * @return EntityHydrator The built entity hydrator. */ public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata); /** * Build a collection hydrator * * @param mixed[] $mapping The association mapping. * * @return CollectionHydrator The built collection hydrator. */ public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping); /** * Build a cache region * * @param array<string,mixed> $cache The cache configuration. * * @return Region The cache region. */ public function getRegion(array $cache); /** * Build timestamp cache region * * @return TimestampRegion The timestamp region. */ public function getTimestampRegion(); /** * Build \Doctrine\ORM\Cache * * @return Cache */ public function createCache(EntityManagerInterface $entityManager); } orm/lib/Doctrine/ORM/Cache/CacheKey.php 0000644 00000001557 15120025733 0013511 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Deprecations\Deprecation; /** * Defines entity / collection / query key to be stored in the cache region. * Allows multiple roles to be stored in the same cache region. */ abstract class CacheKey { /** * Unique identifier * * @readonly Public only for performance reasons, it should be considered immutable. * @var string */ public $hash; public function __construct(?string $hash = null) { if ($hash === null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10212', 'Calling %s() without providing a value for the $hash parameter is deprecated.', __METHOD__ ); } else { $this->hash = $hash; } } } orm/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php 0000644 00000001676 15120025733 0016100 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Collection cache entry */ class CollectionCacheEntry implements CacheEntry { /** * The list of entity identifiers hold by the collection * * @readonly Public only for performance reasons, it should be considered immutable. * @var CacheKey[] */ public $identifiers; /** @param CacheKey[] $identifiers List of entity identifiers hold by the collection */ public function __construct(array $identifiers) { $this->identifiers = $identifiers; } /** * Creates a new CollectionCacheEntry * * This method allows for Doctrine\Common\Cache\PhpFileCache compatibility * * @param array<string, mixed> $values array containing property values * * @return CollectionCacheEntry */ public static function __set_state(array $values) { return new self($values['identifiers']); } } orm/lib/Doctrine/ORM/Cache/CollectionCacheKey.php 0000644 00000003161 15120025733 0015516 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function implode; use function ksort; use function str_replace; use function strtolower; /** * Defines entity collection roles to be stored in the cache region. */ class CollectionCacheKey extends CacheKey { /** * The owner entity identifier * * @readonly Public only for performance reasons, it should be considered immutable. * @var array<string, mixed> */ public $ownerIdentifier; /** * The owner entity class * * @readonly Public only for performance reasons, it should be considered immutable. * @var string * @psalm-var class-string */ public $entityClass; /** * The association name * * @readonly Public only for performance reasons, it should be considered immutable. * @var string */ public $association; /** * @param string $entityClass The entity class. * @param string $association The field name that represents the association. * @param array<string, mixed> $ownerIdentifier The identifier of the owning entity. * @psalm-param class-string $entityClass */ public function __construct($entityClass, $association, array $ownerIdentifier) { ksort($ownerIdentifier); $this->ownerIdentifier = $ownerIdentifier; $this->entityClass = (string) $entityClass; $this->association = (string) $association; parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association); } } orm/lib/Doctrine/ORM/Cache/CollectionHydrator.php 0000644 00000001245 15120025733 0015637 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; /** * Hydrator cache entry for collections */ interface CollectionHydrator { /** * @param array|mixed[]|Collection $collection The collection. * * @return CollectionCacheEntry */ public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection); /** @return mixed[]|null */ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection); } orm/lib/Doctrine/ORM/Cache/ConcurrentRegion.php 0000644 00000002272 15120025733 0015316 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Defines contract for concurrently managed data region. * It should be able to lock an specific cache entry in an atomic operation. * * When a entry is locked another process should not be able to read or write the entry. * All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock. */ interface ConcurrentRegion extends Region { /** * Attempts to read lock the mapping for the given key. * * @param CacheKey $key The key of the item to lock. * * @return Lock|null A lock instance or NULL if the lock already exists. * * @throws LockException Indicates a problem accessing the region. */ public function lock(CacheKey $key); /** * Attempts to read unlock the mapping for the given key. * * @param CacheKey $key The key of the item to unlock. * @param Lock $lock The lock previously obtained from {@link readLock} * * @return bool * * @throws LockException Indicates a problem accessing the region. */ public function unlock(CacheKey $key, Lock $lock); } orm/lib/Doctrine/ORM/Cache/DefaultCache.php 0000644 00000020700 15120025733 0014334 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\ORMInvalidArgumentException; use Doctrine\ORM\UnitOfWork; use function is_array; use function is_object; /** * Provides an API for querying/managing the second level cache regions. */ class DefaultCache implements Cache { /** @var EntityManagerInterface */ private $em; /** @var UnitOfWork */ private $uow; /** @var CacheFactory */ private $cacheFactory; /** * @var QueryCache[] * @psalm-var array<string, QueryCache> */ private $queryCaches = []; /** @var QueryCache|null */ private $defaultQueryCache; public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->cacheFactory = $em->getConfiguration() ->getSecondLevelCacheConfiguration() ->getCacheFactory(); } /** * {@inheritDoc} */ public function getEntityCacheRegion($className) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if (! ($persister instanceof CachedPersister)) { return null; } return $persister->getCacheRegion(); } /** * {@inheritDoc} */ public function getCollectionCacheRegion($className, $association) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if (! ($persister instanceof CachedPersister)) { return null; } return $persister->getCacheRegion(); } /** * {@inheritDoc} */ public function containsEntity($className, $identifier) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if (! ($persister instanceof CachedPersister)) { return false; } return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier)); } /** * {@inheritDoc} */ public function evictEntity($className, $identifier) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if (! ($persister instanceof CachedPersister)) { return; } $persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier)); } /** * {@inheritDoc} */ public function evictEntityRegion($className) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if (! ($persister instanceof CachedPersister)) { return; } $persister->getCacheRegion()->evictAll(); } /** * {@inheritDoc} */ public function evictEntityRegions() { $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); foreach ($metadatas as $metadata) { $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if (! ($persister instanceof CachedPersister)) { continue; } $persister->getCacheRegion()->evictAll(); } } /** * {@inheritDoc} */ public function containsCollection($className, $association, $ownerIdentifier) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if (! ($persister instanceof CachedPersister)) { return false; } return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); } /** * {@inheritDoc} */ public function evictCollection($className, $association, $ownerIdentifier) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if (! ($persister instanceof CachedPersister)) { return; } $persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier)); } /** * {@inheritDoc} */ public function evictCollectionRegion($className, $association) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if (! ($persister instanceof CachedPersister)) { return; } $persister->getCacheRegion()->evictAll(); } /** * {@inheritDoc} */ public function evictCollectionRegions() { $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); foreach ($metadatas as $metadata) { foreach ($metadata->associationMappings as $association) { if (! $association['type'] & ClassMetadata::TO_MANY) { continue; } $persister = $this->uow->getCollectionPersister($association); if (! ($persister instanceof CachedPersister)) { continue; } $persister->getCacheRegion()->evictAll(); } } } /** * {@inheritDoc} */ public function containsQuery($regionName) { return isset($this->queryCaches[$regionName]); } /** * {@inheritDoc} */ public function evictQueryRegion($regionName = null) { if ($regionName === null && $this->defaultQueryCache !== null) { $this->defaultQueryCache->clear(); return; } if (isset($this->queryCaches[$regionName])) { $this->queryCaches[$regionName]->clear(); } } /** * {@inheritDoc} */ public function evictQueryRegions() { $this->getQueryCache()->clear(); foreach ($this->queryCaches as $queryCache) { $queryCache->clear(); } } /** * {@inheritDoc} */ public function getQueryCache($regionName = null) { if ($regionName === null) { return $this->defaultQueryCache ?: $this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em); } if (! isset($this->queryCaches[$regionName])) { $this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName); } return $this->queryCaches[$regionName]; } /** @param mixed $identifier The entity identifier. */ private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey { if (! is_array($identifier)) { $identifier = $this->toIdentifierArray($metadata, $identifier); } return new EntityCacheKey($metadata->rootEntityName, $identifier); } /** @param mixed $ownerIdentifier The identifier of the owning entity. */ private function buildCollectionCacheKey( ClassMetadata $metadata, string $association, $ownerIdentifier ): CollectionCacheKey { if (! is_array($ownerIdentifier)) { $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier); } return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); } /** * @param mixed $identifier The entity identifier. * * @return array<string, mixed> */ private function toIdentifierArray(ClassMetadata $metadata, $identifier): array { if (is_object($identifier)) { $class = ClassUtils::getClass($identifier); if ($this->em->getMetadataFactory()->hasMetadataFor($class)) { $identifier = $this->uow->getSingleIdentifierValue($identifier); if ($identifier === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class); } } } return [$metadata->identifier[0] => $identifier]; } } orm/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php 0000644 00000020403 15120025733 0015664 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Common\Cache\Cache as LegacyCache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister; use Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister; use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister; use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister; use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister; use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\ORM\Cache\Region\FileLockRegion; use Doctrine\ORM\Cache\Region\UpdateTimestampCache; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\Collection\CollectionPersister; use Doctrine\ORM\Persisters\Entity\EntityPersister; use InvalidArgumentException; use LogicException; use Psr\Cache\CacheItemPoolInterface; use TypeError; use function assert; use function get_debug_type; use function sprintf; use const DIRECTORY_SEPARATOR; class DefaultCacheFactory implements CacheFactory { /** @var CacheItemPoolInterface */ private $cacheItemPool; /** @var RegionsConfiguration */ private $regionsConfig; /** @var TimestampRegion|null */ private $timestampRegion; /** @var Region[] */ private $regions = []; /** @var string|null */ private $fileLockRegionDirectory; /** @param CacheItemPoolInterface $cacheItemPool */ public function __construct(RegionsConfiguration $cacheConfig, $cacheItemPool) { if ($cacheItemPool instanceof LegacyCache) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9322', 'Passing an instance of %s to %s is deprecated, pass a %s instead.', get_debug_type($cacheItemPool), __METHOD__, CacheItemPoolInterface::class ); $this->cacheItemPool = CacheAdapter::wrap($cacheItemPool); } elseif (! $cacheItemPool instanceof CacheItemPoolInterface) { throw new TypeError(sprintf( '%s: Parameter #2 is expected to be an instance of %s, got %s.', __METHOD__, CacheItemPoolInterface::class, get_debug_type($cacheItemPool) )); } else { $this->cacheItemPool = $cacheItemPool; } $this->regionsConfig = $cacheConfig; } /** * @param string $fileLockRegionDirectory * * @return void */ public function setFileLockRegionDirectory($fileLockRegionDirectory) { $this->fileLockRegionDirectory = (string) $fileLockRegionDirectory; } /** @return string */ public function getFileLockRegionDirectory() { return $this->fileLockRegionDirectory; } /** @return void */ public function setRegion(Region $region) { $this->regions[$region->getName()] = $region; } /** @return void */ public function setTimestampRegion(TimestampRegion $region) { $this->timestampRegion = $region; } /** * {@inheritDoc} */ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata) { assert($metadata->cache !== null); $region = $this->getRegion($metadata->cache); $usage = $metadata->cache['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata); } if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { if (! $region instanceof ConcurrentRegion) { throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); } return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata); } throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); } /** * {@inheritDoc} */ public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping) { assert(isset($mapping['cache'])); $usage = $mapping['cache']['usage']; $region = $this->getRegion($mapping['cache']); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); } if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { if (! $region instanceof ConcurrentRegion) { throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage)); } return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); } throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage)); } /** * {@inheritDoc} */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { return new DefaultQueryCache( $em, $this->getRegion( [ 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE, ] ) ); } /** * {@inheritDoc} */ public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping) { return new DefaultCollectionHydrator($em); } /** * {@inheritDoc} */ public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata) { return new DefaultEntityHydrator($em); } /** * {@inheritDoc} */ public function getRegion(array $cache) { if (isset($this->regions[$cache['region']])) { return $this->regions[$cache['region']]; } $name = $cache['region']; $lifetime = $this->regionsConfig->getLifetime($cache['region']); $region = new DefaultRegion($name, $this->cacheItemPool, $lifetime); if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) { if ( $this->fileLockRegionDirectory === '' || $this->fileLockRegionDirectory === null ) { throw new LogicException( 'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' . 'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). ' ); } $directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region']; $region = new FileLockRegion($region, $directory, (string) $this->regionsConfig->getLockLifetime($cache['region'])); } return $this->regions[$cache['region']] = $region; } /** * {@inheritDoc} */ public function getTimestampRegion() { if ($this->timestampRegion === null) { $name = Cache::DEFAULT_TIMESTAMP_REGION_NAME; $lifetime = $this->regionsConfig->getLifetime($name); $this->timestampRegion = new UpdateTimestampCache($name, $this->cacheItemPool, $lifetime); } return $this->timestampRegion; } /** * {@inheritDoc} */ public function createCache(EntityManagerInterface $entityManager) { return new DefaultCache($entityManager); } } orm/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php 0000644 00000004477 15120025733 0017156 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; use Doctrine\ORM\UnitOfWork; use function assert; /** * Default hydrator cache for collections */ class DefaultCollectionHydrator implements CollectionHydrator { /** @var EntityManagerInterface */ private $em; /** @var UnitOfWork */ private $uow; /** @var array<string,mixed> */ private static $hints = [Query::HINT_CACHE_ENABLED => true]; /** @param EntityManagerInterface $em The entity manager. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->uow = $em->getUnitOfWork(); } /** * {@inheritDoc} */ public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection) { $data = []; foreach ($collection as $index => $entity) { $data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity)); } return new CollectionCacheEntry($data); } /** * {@inheritDoc} */ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection) { $assoc = $metadata->associationMappings[$key->association]; $targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']); assert($targetPersister instanceof CachedPersister); $targetRegion = $targetPersister->getCacheRegion(); $list = []; /** @var EntityCacheEntry[]|null $entityEntries */ $entityEntries = $targetRegion->getMultiple($entry); if ($entityEntries === null) { return null; } foreach ($entityEntries as $index => $entityEntry) { $entity = $this->uow->createEntity( $entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints ); $collection->hydrateSet($index, $entity); $list[$index] = $entity; } $this->uow->hydrationComplete(); return $list; } } orm/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php 0000644 00000015156 15120025733 0016333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Utility\IdentifierFlattener; use function array_merge; use function assert; use function is_array; use function is_object; use function reset; /** * Default hydrator cache for entities */ class DefaultEntityHydrator implements EntityHydrator { /** @var EntityManagerInterface */ private $em; /** @var UnitOfWork */ private $uow; /** * The IdentifierFlattener used for manipulating identifiers * * @var IdentifierFlattener */ private $identifierFlattener; /** @var array<string,mixed> */ private static $hints = [Query::HINT_CACHE_ENABLED => true]; /** @param EntityManagerInterface $em The entity manager. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); } /** * {@inheritDoc} */ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity) { $data = $this->uow->getOriginalEntityData($entity); $data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ? if ($metadata->requiresFetchAfterChange) { if ($metadata->isVersioned) { assert($metadata->versionField !== null); $data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField); } foreach ($metadata->fieldMappings as $name => $fieldMapping) { if (isset($fieldMapping['generated'])) { $data[$name] = $metadata->getFieldValue($entity, $name); } } } foreach ($metadata->associationMappings as $name => $assoc) { if (! isset($data[$name])) { continue; } if (! ($assoc['type'] & ClassMetadata::TO_ONE)) { unset($data[$name]); continue; } if (! isset($assoc['cache'])) { $targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']); $owningAssociation = ! $assoc['isOwningSide'] ? $targetClassMetadata->associationMappings[$assoc['mappedBy']] : $assoc; $associationIds = $this->identifierFlattener->flattenIdentifier( $targetClassMetadata, $targetClassMetadata->getIdentifierValues($data[$name]) ); unset($data[$name]); foreach ($associationIds as $fieldName => $fieldValue) { if (isset($targetClassMetadata->fieldMappings[$fieldName])) { $fieldMapping = $targetClassMetadata->fieldMappings[$fieldName]; $data[$owningAssociation['targetToSourceKeyColumns'][$fieldMapping['columnName']]] = $fieldValue; continue; } $targetAssoc = $targetClassMetadata->associationMappings[$fieldName]; foreach ($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) { if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) { $data[$localColumn] = $fieldValue; } } } continue; } if (! isset($assoc['id'])) { $targetClass = ClassUtils::getClass($data[$name]); $targetId = $this->uow->getEntityIdentifier($data[$name]); $data[$name] = new AssociationCacheEntry($targetClass, $targetId); continue; } // handle association identifier $targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name]) ? $this->uow->getEntityIdentifier($data[$name]) : $data[$name]; // @TODO - fix it ! // handle UnitOfWork#createEntity hash generation if (! is_array($targetId)) { $data[reset($assoc['joinColumnFieldNames'])] = $targetId; $targetEntity = $this->em->getClassMetadata($assoc['targetEntity']); $targetId = [$targetEntity->identifier[0] => $targetId]; } $data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId); } return new EntityCacheEntry($metadata->name, $data); } /** * {@inheritDoc} */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null) { $data = $entry->data; $hints = self::$hints; if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; } foreach ($metadata->associationMappings as $name => $assoc) { if (! isset($assoc['cache']) || ! isset($data[$name])) { continue; } $assocClass = $data[$name]->class; $assocId = $data[$name]->identifier; $isEagerLoad = ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide'])); if (! $isEagerLoad) { $data[$name] = $this->em->getReference($assocClass, $assocId); continue; } $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId); $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocRegion = $assocPersister->getCacheRegion(); $assocEntry = $assocRegion->get($assocKey); if ($assocEntry === null) { return null; } $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints); } if ($entity !== null) { $this->uow->registerManaged($entity, $key->identifier, $data); } $result = $this->uow->createEntity($entry->class, $data, $hints); $this->uow->hydrationComplete(); return $result; } } orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php 0000644 00000037373 15120025733 0015400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Exception\FeatureNotImplemented; use Doctrine\ORM\Cache\Exception\NonCacheableEntity; use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\UnitOfWork; use Doctrine\Persistence\Proxy; use function array_map; use function array_shift; use function array_unshift; use function assert; use function count; use function is_array; use function key; use function reset; /** * Default query cache implementation. * * @psalm-import-type AssociationMapping from ClassMetadata */ class DefaultQueryCache implements QueryCache { /** @var EntityManagerInterface */ private $em; /** @var UnitOfWork */ private $uow; /** @var Region */ private $region; /** @var QueryCacheValidator */ private $validator; /** @var CacheLogger|null */ protected $cacheLogger; /** @var array<string,mixed> */ private static $hints = [Query::HINT_CACHE_ENABLED => true]; /** * @param EntityManagerInterface $em The entity manager. * @param Region $region The query region. */ public function __construct(EntityManagerInterface $em, Region $region) { $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration(); $this->em = $em; $this->region = $region; $this->uow = $em->getUnitOfWork(); $this->cacheLogger = $cacheConfig->getCacheLogger(); $this->validator = $cacheConfig->getQueryValidator(); } /** * {@inheritDoc} */ public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []) { if (! ($key->cacheMode & Cache::MODE_GET)) { return null; } $cacheEntry = $this->region->get($key); if (! $cacheEntry instanceof QueryCacheEntry) { return null; } if (! $this->validator->isValid($key, $cacheEntry)) { $this->region->evict($key); return null; } $result = []; $entityName = reset($rsm->aliasMap); $hasRelation = ! empty($rsm->relationMap); $persister = $this->uow->getEntityPersister($entityName); assert($persister instanceof CachedEntityPersister); $region = $persister->getCacheRegion(); $regionName = $region->getName(); $cm = $this->em->getClassMetadata($entityName); $generateKeys = static function (array $entry) use ($cm): EntityCacheKey { return new EntityCacheKey($cm->rootEntityName, $entry['identifier']); }; $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result)); $entries = $region->getMultiple($cacheKeys) ?? []; // @TODO - move to cache hydration component foreach ($cacheEntry->result as $index => $entry) { $entityEntry = $entries[$index] ?? null; if (! $entityEntry instanceof EntityCacheEntry) { if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]); } return null; } if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]); } if (! $hasRelation) { $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints); continue; } $data = $entityEntry->data; foreach ($entry['associations'] as $name => $assoc) { $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); assert($assocPersister instanceof CachedEntityPersister); $assocRegion = $assocPersister->getCacheRegion(); $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); if ($assoc['type'] & ClassMetadata::TO_ONE) { $assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']); $assocEntry = $assocRegion->get($assocKey); if ($assocEntry === null) { if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); } $this->uow->hydrationComplete(); return null; } $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey); } continue; } if (! isset($assoc['list']) || empty($assoc['list'])) { continue; } $generateKeys = static function ($id) use ($assocMetadata): EntityCacheKey { return new EntityCacheKey($assocMetadata->rootEntityName, $id); }; $collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection()); $assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list'])); $assocEntries = $assocRegion->getMultiple($assocKeys); foreach ($assoc['list'] as $assocIndex => $assocId) { $assocEntry = is_array($assocEntries) ? ($assocEntries[$assocIndex] ?? null) : null; if ($assocEntry === null) { if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); } $this->uow->hydrationComplete(); return null; } $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); $collection->hydrateSet($assocIndex, $element); if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); } } $data[$name] = $collection; $collection->setInitialized(true); } foreach ($data as $fieldName => $unCachedAssociationData) { // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the // cache key information in `$cacheEntry` will not contain details // for fields that are associations. // // This means that `$data` keys for some associations that may // actually not be cached will not be converted to actual association // data, yet they contain L2 cache AssociationCacheEntry objects. // // We need to unwrap those associations into proxy references, // since we don't have actual data for them except for identifiers. if ($unCachedAssociationData instanceof AssociationCacheEntry) { $data[$fieldName] = $this->em->getReference( $unCachedAssociationData->class, $unCachedAssociationData->identifier ); } } $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); } $this->uow->hydrationComplete(); return $result; } /** * {@inheritDoc} */ public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []) { if ($rsm->scalarMappings) { throw FeatureNotImplemented::scalarResults(); } if (count($rsm->entityMappings) > 1) { throw FeatureNotImplemented::multipleRootEntities(); } if (! $rsm->isSelect) { throw FeatureNotImplemented::nonSelectStatements(); } if (($hints[Query\SqlWalker::HINT_PARTIAL] ?? false) === true || ($hints[Query::HINT_FORCE_PARTIAL_LOAD] ?? false) === true) { throw FeatureNotImplemented::partialEntities(); } if (! ($key->cacheMode & Cache::MODE_PUT)) { return false; } $data = []; $entityName = reset($rsm->aliasMap); $rootAlias = key($rsm->aliasMap); $persister = $this->uow->getEntityPersister($entityName); if (! $persister instanceof CachedEntityPersister) { throw NonCacheableEntity::fromEntity($entityName); } $region = $persister->getCacheRegion(); $cm = $this->em->getClassMetadata($entityName); assert($cm instanceof ClassMetadata); foreach ($result as $index => $entity) { $identifier = $this->uow->getEntityIdentifier($entity); $entityKey = new EntityCacheKey($cm->rootEntityName, $identifier); if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { // Cancel put result if entity put fail if (! $persister->storeEntityCache($entity, $entityKey)) { return false; } } $data[$index]['identifier'] = $identifier; $data[$index]['associations'] = []; // @TODO - move to cache hydration components foreach ($rsm->relationMap as $alias => $name) { $parentAlias = $rsm->parentAliasMap[$alias]; $parentClass = $rsm->aliasMap[$parentAlias]; $metadata = $this->em->getClassMetadata($parentClass); $assoc = $metadata->associationMappings[$name]; $assocValue = $this->getAssociationValue($rsm, $alias, $entity); if ($assocValue === null) { continue; } // root entity association if ($rootAlias === $parentAlias) { // Cancel put result if association put fail $assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue); if ($assocInfo === null) { return false; } $data[$index]['associations'][$name] = $assocInfo; continue; } // store single nested association if (! is_array($assocValue)) { // Cancel put result if association put fail if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) { return false; } continue; } // store array of nested association foreach ($assocValue as $aVal) { // Cancel put result if association put fail if ($this->storeAssociationCache($key, $assoc, $aVal) === null) { return false; } } } } return $this->region->put($key, new QueryCacheEntry($data)); } /** * @param AssociationMapping $assoc * @param mixed $assocValue * * @return mixed[]|null * @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null */ private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue): ?array { $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocMetadata = $assocPersister->getClassMetadata(); $assocRegion = $assocPersister->getCacheRegion(); // Handle *-to-one associations if ($assoc['type'] & ClassMetadata::TO_ONE) { $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); if (! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { // Entity put fail if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) { return null; } } return [ 'targetEntity' => $assocMetadata->rootEntityName, 'identifier' => $assocIdentifier, 'type' => $assoc['type'], ]; } // Handle *-to-many associations $list = []; foreach ($assocValue as $assocItemIndex => $assocItem) { $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { // Entity put fail if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) { return null; } } $list[$assocItemIndex] = $assocIdentifier; } return [ 'targetEntity' => $assocMetadata->rootEntityName, 'type' => $assoc['type'], 'list' => $list, ]; } /** * @param object $entity * * @return mixed[]|object|null * @psalm-return list<mixed>|object|null */ private function getAssociationValue( ResultSetMapping $rsm, string $assocAlias, $entity ) { $path = []; $alias = $assocAlias; while (isset($rsm->parentAliasMap[$alias])) { $parent = $rsm->parentAliasMap[$alias]; $field = $rsm->relationMap[$alias]; $class = $rsm->aliasMap[$parent]; array_unshift($path, [ 'field' => $field, 'class' => $class, ]); $alias = $parent; } return $this->getAssociationPathValue($entity, $path); } /** * @param mixed $value * @psalm-param array<array-key, array{field: string, class: string}> $path * * @return mixed[]|object|null * @psalm-return list<mixed>|object|null */ private function getAssociationPathValue($value, array $path) { $mapping = array_shift($path); $metadata = $this->em->getClassMetadata($mapping['class']); $assoc = $metadata->associationMappings[$mapping['field']]; $value = $metadata->getFieldValue($value, $mapping['field']); if ($value === null) { return null; } if ($path === []) { return $value; } // Handle *-to-one associations if ($assoc['type'] & ClassMetadata::TO_ONE) { return $this->getAssociationPathValue($value, $path); } $values = []; foreach ($value as $item) { $values[] = $this->getAssociationPathValue($item, $path); } return $values; } /** * {@inheritDoc} */ public function clear() { return $this->region->evictAll(); } /** * {@inheritDoc} */ public function getRegion() { return $this->region; } } orm/lib/Doctrine/ORM/Cache/EntityCacheEntry.php 0000644 00000003345 15120025733 0015254 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\EntityManagerInterface; use function array_map; /** * Entity cache entry */ class EntityCacheEntry implements CacheEntry { /** * The entity map data * * @readonly Public only for performance reasons, it should be considered immutable. * @var array<string,mixed> */ public $data; /** * The entity class name * * @readonly Public only for performance reasons, it should be considered immutable. * @var string * @psalm-var class-string */ public $class; /** * @param string $class The entity class. * @param array<string,mixed> $data The entity data. * @psalm-param class-string $class */ public function __construct($class, array $data) { $this->class = $class; $this->data = $data; } /** * Creates a new EntityCacheEntry * * This method allow Doctrine\Common\Cache\PhpFileCache compatibility * * @param array<string,mixed> $values array containing property values * * @return EntityCacheEntry */ public static function __set_state(array $values) { return new self($values['class'], $values['data']); } /** * Retrieves the entity data resolving cache entries * * @return array<string, mixed> */ public function resolveAssociationEntries(EntityManagerInterface $em) { return array_map(static function ($value) use ($em) { if (! ($value instanceof AssociationCacheEntry)) { return $value; } return $em->getReference($value->class, $value->identifier); }, $this->data); } } orm/lib/Doctrine/ORM/Cache/EntityCacheKey.php 0000644 00000002364 15120025733 0014703 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function implode; use function ksort; use function str_replace; use function strtolower; /** * Defines entity classes roles to be stored in the cache region. */ class EntityCacheKey extends CacheKey { /** * The entity identifier * * @readonly Public only for performance reasons, it should be considered immutable. * @var array<string, mixed> */ public $identifier; /** * The entity class name * * @readonly Public only for performance reasons, it should be considered immutable. * @var string * @psalm-var class-string */ public $entityClass; /** * @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. * @param array<string, mixed> $identifier The entity identifier * @psalm-param class-string $entityClass */ public function __construct($entityClass, array $identifier) { ksort($identifier); $this->identifier = $identifier; $this->entityClass = $entityClass; parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier))); } } orm/lib/Doctrine/ORM/Cache/EntityHydrator.php 0000644 00000001725 15120025733 0015023 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Mapping\ClassMetadata; /** * Hydrator cache entry for entities */ interface EntityHydrator { /** * @param ClassMetadata $metadata The entity metadata. * @param EntityCacheKey $key The entity cache key. * @param object $entity The entity. * * @return EntityCacheEntry */ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity); /** * @param ClassMetadata $metadata The entity metadata. * @param EntityCacheKey $key The entity cache key. * @param EntityCacheEntry $entry The entity cache entry. * @param object|null $entity The entity to load the cache into. If not specified, a new entity is created. * * @return object|null */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null); } orm/lib/Doctrine/ORM/Cache/Lock.php 0000644 00000000750 15120025733 0012717 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function time; use function uniqid; class Lock { /** @var string */ public $value; /** @var int */ public $time; public function __construct(string $value, ?int $time = null) { $this->value = $value; $this->time = $time ?: time(); } /** @return Lock */ public static function createLockRead() { return new self(uniqid((string) time(), true)); } } orm/lib/Doctrine/ORM/Cache/LockException.php 0000644 00000000306 15120025733 0014573 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Exception\CacheException; /** * Lock exception for cache. */ class LockException extends CacheException { } orm/lib/Doctrine/ORM/Cache/MultiGetRegion.php 0000644 00000001227 15120025733 0014725 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Defines a region that supports multi-get reading. * * With one method call we can get multiple items. * * @deprecated Implement {@see Region} instead. */ interface MultiGetRegion { /** * Get all items from the cache identified by $keys. * It returns NULL if some elements can not be found. * * @param CollectionCacheEntry $collection The collection of the items to be retrieved. * * @return CacheEntry[]|null The cached entries or NULL if one or more entries can not be found */ public function getMultiple(CollectionCacheEntry $collection); } orm/lib/Doctrine/ORM/Cache/QueryCache.php 0000644 00000001405 15120025733 0014056 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Query\ResultSetMapping; /** * Defines the contract for caches capable of storing query results. * These caches should only concern themselves with storing the matching result ids. */ interface QueryCache { /** @return bool */ public function clear(); /** * @param mixed $result * @param mixed[] $hints * * @return bool */ public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []); /** * @param mixed[] $hints * * @return mixed[]|null */ public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []); /** @return Region */ public function getRegion(); } orm/lib/Doctrine/ORM/Cache/QueryCacheEntry.php 0000644 00000002007 15120025733 0015077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function microtime; /** * Query cache entry */ class QueryCacheEntry implements CacheEntry { /** * List of entity identifiers * * @readonly Public only for performance reasons, it should be considered immutable. * @var array<string, mixed> */ public $result; /** * Time creation of this cache entry * * @readonly Public only for performance reasons, it should be considered immutable. * @var float */ public $time; /** * @param array<string, mixed> $result * @param float|null $time */ public function __construct($result, $time = null) { $this->result = $result; $this->time = $time ?: microtime(true); } /** * @param array<string, mixed> $values * * @return QueryCacheEntry */ public static function __set_state(array $values) { return new self($values['result'], $values['time']); } } orm/lib/Doctrine/ORM/Cache/QueryCacheKey.php 0000644 00000002170 15120025733 0014527 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache; /** * A cache key that identifies a particular query. */ class QueryCacheKey extends CacheKey { /** * Cache key lifetime * * @readonly Public only for performance reasons, it should be considered immutable. * @var int */ public $lifetime; /** * Cache mode * * @readonly Public only for performance reasons, it should be considered immutable. * @var int * @psalm-var Cache::MODE_* */ public $cacheMode; /** * @readonly Public only for performance reasons, it should be considered immutable. * @var TimestampCacheKey|null */ public $timestampKey; /** @psalm-param Cache::MODE_* $cacheMode */ public function __construct( string $cacheId, int $lifetime = 0, int $cacheMode = Cache::MODE_NORMAL, ?TimestampCacheKey $timestampKey = null ) { $this->lifetime = $lifetime; $this->cacheMode = $cacheMode; $this->timestampKey = $timestampKey; parent::__construct($cacheId); } } orm/lib/Doctrine/ORM/Cache/QueryCacheValidator.php 0000644 00000000454 15120025733 0015727 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Cache query validator interface. */ interface QueryCacheValidator { /** * Checks if the query entry is valid * * @return bool */ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry); } orm/lib/Doctrine/ORM/Cache/Region.php 0000644 00000003526 15120025733 0013256 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\Exception\CacheException; /** * Defines a contract for accessing a particular named region. */ interface Region extends MultiGetRegion { /** * Retrieve the name of this region. * * @return string The region name */ public function getName(); /** * Determine whether this region contains data for the given key. * * @param CacheKey $key The cache key * * @return bool TRUE if the underlying cache contains corresponding data; FALSE otherwise. */ public function contains(CacheKey $key); /** * Get an item from the cache. * * @param CacheKey $key The key of the item to be retrieved. * * @return CacheEntry|null The cached entry or NULL * * @throws CacheException Indicates a problem accessing the item or region. */ public function get(CacheKey $key); /** * Put an item into the cache. * * @param CacheKey $key The key under which to cache the item. * @param CacheEntry $entry The entry to cache. * @param Lock|null $lock The lock previously obtained. * * @return bool * * @throws CacheException Indicates a problem accessing the region. */ public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null); /** * Remove an item from the cache. * * @param CacheKey $key The key under which to cache the item. * * @return bool * * @throws CacheException Indicates a problem accessing the region. */ public function evict(CacheKey $key); /** * Remove all contents of this particular cache region. * * @return bool * * @throws CacheException Indicates problem accessing the region. */ public function evictAll(); } orm/lib/Doctrine/ORM/Cache/RegionsConfiguration.php 0000644 00000004246 15120025733 0016171 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Cache regions configuration */ class RegionsConfiguration { /** @var array<string,int> */ private $lifetimes = []; /** @var array<string,int> */ private $lockLifetimes = []; /** @var int */ private $defaultLifetime; /** @var int */ private $defaultLockLifetime; /** * @param int $defaultLifetime * @param int $defaultLockLifetime */ public function __construct($defaultLifetime = 3600, $defaultLockLifetime = 60) { $this->defaultLifetime = (int) $defaultLifetime; $this->defaultLockLifetime = (int) $defaultLockLifetime; } /** @return int */ public function getDefaultLifetime() { return $this->defaultLifetime; } /** * @param int $defaultLifetime * * @return void */ public function setDefaultLifetime($defaultLifetime) { $this->defaultLifetime = (int) $defaultLifetime; } /** @return int */ public function getDefaultLockLifetime() { return $this->defaultLockLifetime; } /** * @param int $defaultLockLifetime * * @return void */ public function setDefaultLockLifetime($defaultLockLifetime) { $this->defaultLockLifetime = (int) $defaultLockLifetime; } /** * @param string $regionName * * @return int */ public function getLifetime($regionName) { return $this->lifetimes[$regionName] ?? $this->defaultLifetime; } /** * @param string $name * @param int $lifetime * * @return void */ public function setLifetime($name, $lifetime) { $this->lifetimes[$name] = (int) $lifetime; } /** * @param string $regionName * * @return int */ public function getLockLifetime($regionName) { return $this->lockLifetimes[$regionName] ?? $this->defaultLockLifetime; } /** * @param string $name * @param int $lifetime * * @return void */ public function setLockLifetime($name, $lifetime) { $this->lockLifetimes[$name] = (int) $lifetime; } } orm/lib/Doctrine/ORM/Cache/TimestampCacheEntry.php 0000644 00000001513 15120025733 0015736 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function microtime; /** * Timestamp cache entry */ class TimestampCacheEntry implements CacheEntry { /** * @readonly Public only for performance reasons, it should be considered immutable. * @var float */ public $time; /** @param float|null $time */ public function __construct($time = null) { $this->time = $time ? (float) $time : microtime(true); } /** * Creates a new TimestampCacheEntry * * This method allow Doctrine\Common\Cache\PhpFileCache compatibility * * @param array<string,float> $values array containing property values * * @return TimestampCacheEntry */ public static function __set_state(array $values) { return new self($values['time']); } } orm/lib/Doctrine/ORM/Cache/TimestampCacheKey.php 0000644 00000000465 15120025733 0015372 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * A key that identifies a timestamped space. */ class TimestampCacheKey extends CacheKey { /** @param string $space Result cache id */ public function __construct($space) { parent::__construct((string) $space); } } orm/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php 0000644 00000001757 15120025733 0017622 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; use function microtime; class TimestampQueryCacheValidator implements QueryCacheValidator { /** @var TimestampRegion */ private $timestampRegion; public function __construct(TimestampRegion $timestampRegion) { $this->timestampRegion = $timestampRegion; } /** * {@inheritDoc} */ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) { if ($this->regionUpdated($key, $entry)) { return false; } if ($key->lifetime === 0) { return true; } return $entry->time + $key->lifetime > microtime(true); } private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry): bool { if ($key->timestampKey === null) { return false; } $timestamp = $this->timestampRegion->get($key->timestampKey); return $timestamp && $timestamp->time > $entry->time; } } orm/lib/Doctrine/ORM/Cache/TimestampRegion.php 0000644 00000000675 15120025733 0015144 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Cache; /** * Defines the contract for a cache region which will specifically be used to store entity "update timestamps". */ interface TimestampRegion extends Region { /** * Update a specific key into the cache region. * * @return void * * @throws LockException Indicates a problem accessing the region. */ public function update(CacheKey $key); } orm/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php 0000644 00000014074 15120025733 0017364 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Decorator; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ObjectManagerDecorator; use function func_get_arg; use function func_num_args; use function get_debug_type; use function method_exists; use function sprintf; use function trigger_error; use const E_USER_NOTICE; /** * Base class for EntityManager decorators * * @extends ObjectManagerDecorator<EntityManagerInterface> */ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface { public function __construct(EntityManagerInterface $wrapped) { $this->wrapped = $wrapped; } /** * {@inheritDoc} */ public function getConnection() { return $this->wrapped->getConnection(); } /** * {@inheritDoc} */ public function getExpressionBuilder() { return $this->wrapped->getExpressionBuilder(); } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * * @psalm-return EntityRepository<T> * * @template T of object */ public function getRepository($className) { return $this->wrapped->getRepository($className); } /** * {@inheritDoc} */ public function getClassMetadata($className) { return $this->wrapped->getClassMetadata($className); } /** * {@inheritDoc} */ public function beginTransaction() { $this->wrapped->beginTransaction(); } /** * {@inheritDoc} */ public function transactional($func) { return $this->wrapped->transactional($func); } /** * {@inheritDoc} */ public function wrapInTransaction(callable $func) { if (! method_exists($this->wrapped, 'wrapInTransaction')) { trigger_error( sprintf('Calling `transactional()` instead of `wrapInTransaction()` which is not implemented on %s', get_debug_type($this->wrapped)), E_USER_NOTICE ); return $this->wrapped->transactional($func); } return $this->wrapped->wrapInTransaction($func); } /** * {@inheritDoc} */ public function commit() { $this->wrapped->commit(); } /** * {@inheritDoc} */ public function rollback() { $this->wrapped->rollback(); } /** * {@inheritDoc} */ public function createQuery($dql = '') { return $this->wrapped->createQuery($dql); } /** * {@inheritDoc} */ public function createNamedQuery($name) { return $this->wrapped->createNamedQuery($name); } /** * {@inheritDoc} */ public function createNativeQuery($sql, ResultSetMapping $rsm) { return $this->wrapped->createNativeQuery($sql, $rsm); } /** * {@inheritDoc} */ public function createNamedNativeQuery($name) { return $this->wrapped->createNamedNativeQuery($name); } /** * {@inheritDoc} */ public function createQueryBuilder() { return $this->wrapped->createQueryBuilder(); } /** * {@inheritDoc} */ public function getReference($entityName, $id) { return $this->wrapped->getReference($entityName, $id); } /** * {@inheritDoc} */ public function getPartialReference($entityName, $identifier) { return $this->wrapped->getPartialReference($entityName, $identifier); } /** * {@inheritDoc} */ public function close() { $this->wrapped->close(); } /** * {@inheritDoc} */ public function copy($entity, $deep = false) { return $this->wrapped->copy($entity, $deep); } /** * {@inheritDoc} */ public function lock($entity, $lockMode, $lockVersion = null) { $this->wrapped->lock($entity, $lockMode, $lockVersion); } /** * {@inheritDoc} */ public function find($className, $id, $lockMode = null, $lockVersion = null) { return $this->wrapped->find($className, $id, $lockMode, $lockVersion); } /** * {@inheritDoc} */ public function flush($entity = null) { $this->wrapped->flush($entity); } /** * {@inheritDoc} */ public function refresh($object) { $lockMode = null; if (func_num_args() > 1) { $lockMode = func_get_arg(1); } $this->wrapped->refresh($object, $lockMode); } /** * {@inheritDoc} */ public function getEventManager() { return $this->wrapped->getEventManager(); } /** * {@inheritDoc} */ public function getConfiguration() { return $this->wrapped->getConfiguration(); } /** * {@inheritDoc} */ public function isOpen() { return $this->wrapped->isOpen(); } /** * {@inheritDoc} */ public function getUnitOfWork() { return $this->wrapped->getUnitOfWork(); } /** * {@inheritDoc} */ public function getHydrator($hydrationMode) { return $this->wrapped->getHydrator($hydrationMode); } /** * {@inheritDoc} */ public function newHydrator($hydrationMode) { return $this->wrapped->newHydrator($hydrationMode); } /** * {@inheritDoc} */ public function getProxyFactory() { return $this->wrapped->getProxyFactory(); } /** * {@inheritDoc} */ public function getFilters() { return $this->wrapped->getFilters(); } /** * {@inheritDoc} */ public function isFiltersStateClean() { return $this->wrapped->isFiltersStateClean(); } /** * {@inheritDoc} */ public function hasFilters() { return $this->wrapped->hasFilters(); } /** * {@inheritDoc} */ public function getCache() { return $this->wrapped->getCache(); } } orm/lib/Doctrine/ORM/Event/LifecycleEventArgs.php 0000644 00000003777 15120025733 0015637 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs; /** * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions * of entities. * * @deprecated This class will be removed in ORM 3.0. Use one of the dedicated classes instead. * * @extends BaseLifecycleEventArgs<EntityManagerInterface> */ class LifecycleEventArgs extends BaseLifecycleEventArgs { /** @param object $object */ public function __construct($object, EntityManagerInterface $objectManager) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'The %s class is deprecated and will be removed in ORM 3.0. Use %s instead.', self::class, BaseLifecycleEventArgs::class ); parent::__construct($object, $objectManager); } /** * Retrieves associated Entity. * * @deprecated 2.13. Use {@see getObject} instead. * * @return object */ public function getEntity() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObject() instead.', __METHOD__ ); return $this->getObject(); } /** * Retrieves associated EntityManager. * * @deprecated 2.13. Use {@see getObjectManager} instead. * * @return EntityManagerInterface */ public function getEntityManager() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.', __METHOD__ ); return $this->getObjectManager(); } } orm/lib/Doctrine/ORM/Event/ListenersInvoker.php 0000644 00000005731 15120025733 0015417 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Common\EventArgs; use Doctrine\Common\EventManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\EntityListenerResolver; /** * A method invoker based on entity lifecycle. */ class ListenersInvoker { public const INVOKE_NONE = 0; public const INVOKE_LISTENERS = 1; public const INVOKE_CALLBACKS = 2; public const INVOKE_MANAGER = 4; /** @var EntityListenerResolver The Entity listener resolver. */ private $resolver; /** * The EventManager used for dispatching events. * * @var EventManager */ private $eventManager; public function __construct(EntityManagerInterface $em) { $this->eventManager = $em->getEventManager(); $this->resolver = $em->getConfiguration()->getEntityListenerResolver(); } /** * Get the subscribed event systems * * @param ClassMetadata $metadata The entity metadata. * @param string $eventName The entity lifecycle event. * * @return int Bitmask of subscribed event systems. * @psalm-return int-mask-of<self::INVOKE_*> */ public function getSubscribedSystems(ClassMetadata $metadata, $eventName) { $invoke = self::INVOKE_NONE; if (isset($metadata->lifecycleCallbacks[$eventName])) { $invoke |= self::INVOKE_CALLBACKS; } if (isset($metadata->entityListeners[$eventName])) { $invoke |= self::INVOKE_LISTENERS; } if ($this->eventManager->hasListeners($eventName)) { $invoke |= self::INVOKE_MANAGER; } return $invoke; } /** * Dispatches the lifecycle event of the given entity. * * @param ClassMetadata $metadata The entity metadata. * @param string $eventName The entity lifecycle event. * @param object $entity The Entity on which the event occurred. * @param EventArgs $event The Event args. * @param int $invoke Bitmask to invoke listeners. * @psalm-param int-mask-of<self::INVOKE_*> $invoke * * @return void */ public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke) { if ($invoke & self::INVOKE_CALLBACKS) { foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) { $entity->$callback($event); } } if ($invoke & self::INVOKE_LISTENERS) { foreach ($metadata->entityListeners[$eventName] as $listener) { $class = $listener['class']; $method = $listener['method']; $instance = $this->resolver->resolve($class); $instance->$method($entity, $event); } } if ($invoke & self::INVOKE_MANAGER) { $this->eventManager->dispatchEvent($eventName, $event); } } } orm/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php 0000644 00000001242 15120025733 0017227 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs; /** * Class that holds event arguments for a loadMetadata event. * * @extends BaseLoadClassMetadataEventArgs<ClassMetadata<object>, EntityManagerInterface> */ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs { /** * Retrieve associated EntityManager. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->getObjectManager(); } } orm/lib/Doctrine/ORM/Event/OnClassMetadataNotFoundEventArgs.php 0000644 00000003545 15120025733 0020411 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\ManagerEventArgs; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use function func_num_args; /** * Class that holds event arguments for a `onClassMetadataNotFound` event. * * This object is mutable by design, allowing callbacks having access to it to set the * found metadata in it, and therefore "cancelling" a `onClassMetadataNotFound` event * * @extends ManagerEventArgs<EntityManagerInterface> */ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs { /** @var string */ private $className; /** @var ClassMetadata|null */ private $foundMetadata; /** * @param string $className * @param EntityManagerInterface $objectManager */ public function __construct($className, ObjectManager $objectManager) { $this->className = (string) $className; parent::__construct($objectManager); } /** @return void */ public function setFoundMetadata(?ClassMetadata $classMetadata = null) { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9791', 'Calling %s without arguments is deprecated, pass null instead.', __METHOD__ ); } $this->foundMetadata = $classMetadata; } /** @return ClassMetadata|null */ public function getFoundMetadata() { return $this->foundMetadata; } /** * Retrieve class name for which a failed metadata fetch attempt was executed * * @return string */ public function getClassName() { return $this->className; } } orm/lib/Doctrine/ORM/Event/OnClearEventArgs.php 0000644 00000003541 15120025733 0015250 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\OnClearEventArgs as BaseOnClearEventArgs; /** * Provides event arguments for the onClear event. * * @link www.doctrine-project.org * * @extends BaseOnClearEventArgs<EntityManagerInterface> */ class OnClearEventArgs extends BaseOnClearEventArgs { /** @var string|null */ private $entityClass; /** @param string|null $entityClass Optional entity class. */ public function __construct(EntityManagerInterface $em, $entityClass = null) { parent::__construct($em); $this->entityClass = $entityClass; } /** * Retrieves associated EntityManager. * * @deprecated 2.13. Use {@see getObjectManager} instead. * * @return EntityManagerInterface */ public function getEntityManager() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.', __METHOD__ ); return $this->getObjectManager(); } /** * Name of the entity class that is cleared, or empty if all are cleared. * * @deprecated Clearing the entity manager partially is deprecated. This method will be removed in 3.0. * * @return string|null */ public function getEntityClass() { return $this->entityClass; } /** * Checks if event clears all entities. * * @deprecated Clearing the entity manager partially is deprecated. This method will be removed in 3.0. * * @return bool */ public function clearsAllEntities() { return $this->entityClass === null; } } orm/lib/Doctrine/ORM/Event/OnFlushEventArgs.php 0000644 00000001651 15120025733 0015303 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\ManagerEventArgs; /** * Provides event arguments for the preFlush event. * * @link www.doctrine-project.org * * @extends ManagerEventArgs<EntityManagerInterface> */ class OnFlushEventArgs extends ManagerEventArgs { /** * Retrieve associated EntityManager. * * @deprecated 2.13. Use {@see getObjectManager} instead. * * @return EntityManagerInterface */ public function getEntityManager() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.', __METHOD__ ); return $this->getObjectManager(); } } orm/lib/Doctrine/ORM/Event/PostFlushEventArgs.php 0000644 00000001655 15120025733 0015660 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\ManagerEventArgs; /** * Provides event arguments for the postFlush event. * * @link www.doctrine-project.org * * @extends ManagerEventArgs<EntityManagerInterface> */ class PostFlushEventArgs extends ManagerEventArgs { /** * Retrieves associated EntityManager. * * @deprecated 2.13. Use {@see getObjectManager} instead. * * @return EntityManagerInterface */ public function getEntityManager() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.', __METHOD__ ); return $this->getObjectManager(); } } orm/lib/Doctrine/ORM/Event/PostLoadEventArgs.php 0000644 00000000175 15120025733 0015452 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PostLoadEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PostPersistEventArgs.php 0000644 00000000200 15120025733 0016211 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PostPersistEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PostRemoveEventArgs.php 0000644 00000000177 15120025733 0016032 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PostRemoveEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PostUpdateEventArgs.php 0000644 00000000177 15120025733 0016017 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PostUpdateEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PreFlushEventArgs.php 0000644 00000001571 15120025733 0015456 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Event\ManagerEventArgs; /** * Provides event arguments for the preFlush event. * * @link www.doctrine-project.com * * @extends ManagerEventArgs<EntityManagerInterface> */ class PreFlushEventArgs extends ManagerEventArgs { /** * @deprecated 2.13. Use {@see getObjectManager} instead. * * @return EntityManagerInterface */ public function getEntityManager() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9875', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.', __METHOD__ ); return $this->getObjectManager(); } } orm/lib/Doctrine/ORM/Event/PrePersistEventArgs.php 0000644 00000000177 15120025733 0016027 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PrePersistEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PreRemoveEventArgs.php 0000644 00000000176 15120025733 0015632 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; final class PreRemoveEventArgs extends LifecycleEventArgs { } orm/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php 0000644 00000005145 15120025733 0015620 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Event; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\PersistentCollection; use InvalidArgumentException; use function get_debug_type; use function sprintf; /** * Class that holds event arguments for a preUpdate event. */ class PreUpdateEventArgs extends LifecycleEventArgs { /** @var array<string, array{mixed, mixed}|PersistentCollection> */ private $entityChangeSet; /** * @param object $entity * @param mixed[][] $changeSet * @psalm-param array<string, array{mixed, mixed}|PersistentCollection> $changeSet */ public function __construct($entity, EntityManagerInterface $em, array &$changeSet) { parent::__construct($entity, $em); $this->entityChangeSet = &$changeSet; } /** * Retrieves entity changeset. * * @return mixed[][] * @psalm-return array<string, array{mixed, mixed}|PersistentCollection> */ public function getEntityChangeSet() { return $this->entityChangeSet; } /** * Checks if field has a changeset. * * @param string $field * * @return bool */ public function hasChangedField($field) { return isset($this->entityChangeSet[$field]); } /** * Gets the old value of the changeset of the changed field. * * @param string $field * * @return mixed */ public function getOldValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][0]; } /** * Gets the new value of the changeset of the changed field. * * @param string $field * * @return mixed */ public function getNewValue($field) { $this->assertValidField($field); return $this->entityChangeSet[$field][1]; } /** * Sets the new value of this field. * * @param string $field * @param mixed $value * * @return void */ public function setNewValue($field, $value) { $this->assertValidField($field); $this->entityChangeSet[$field][1] = $value; } /** * Asserts the field exists in changeset. * * @throws InvalidArgumentException */ private function assertValidField(string $field): void { if (! isset($this->entityChangeSet[$field])) { throw new InvalidArgumentException(sprintf( 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', $field, get_debug_type($this->getObject()) )); } } } orm/lib/Doctrine/ORM/Exception/ConfigurationException.php 0000644 00000000213 15120025733 0017442 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Throwable; interface ConfigurationException extends Throwable { } orm/lib/Doctrine/ORM/Exception/EntityManagerClosed.php 0000644 00000000410 15120025733 0016654 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; final class EntityManagerClosed extends ORMException implements ManagerException { public static function create(): self { return new self('The EntityManager is closed.'); } } orm/lib/Doctrine/ORM/Exception/EntityMissingAssignedId.php 0000644 00000001263 15120025733 0017523 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function get_debug_type; final class EntityMissingAssignedId extends ORMException { /** @param object $entity */ public static function forField($entity, string $field): self { return new self('Entity of type ' . get_debug_type($entity) . " is missing an assigned ID for field '" . $field . "'. " . 'The identifier generation strategy for this entity requires the ID field to be populated before ' . 'EntityManager#persist() is called. If you want automatically generated identifiers instead ' . 'you need to adjust the metadata mapping accordingly.'); } } orm/lib/Doctrine/ORM/Exception/InvalidEntityRepository.php 0000644 00000000644 15120025733 0017647 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Doctrine\ORM\EntityRepository; final class InvalidEntityRepository extends ORMException implements ConfigurationException { public static function fromClassName(string $className): self { return new self( "Invalid repository class '" . $className . "'. It must be a " . EntityRepository::class . '.' ); } } orm/lib/Doctrine/ORM/Exception/InvalidHydrationMode.php 0000644 00000000504 15120025733 0017034 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; final class InvalidHydrationMode extends ORMException implements ManagerException { public static function fromMode(string $mode): self { return new self(sprintf('"%s" is an invalid hydration mode.', $mode)); } } orm/lib/Doctrine/ORM/Exception/ManagerException.php 0000644 00000000205 15120025733 0016206 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Throwable; interface ManagerException extends Throwable { } orm/lib/Doctrine/ORM/Exception/MismatchedEventManager.php 0000644 00000000522 15120025733 0017332 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; final class MismatchedEventManager extends ORMException implements ManagerException { public static function create(): self { return new self( 'Cannot use different EventManager instances for EntityManager and Connection.' ); } } orm/lib/Doctrine/ORM/Exception/MissingIdentifierField.php 0000644 00000000662 15120025733 0017344 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; final class MissingIdentifierField extends ORMException implements ManagerException { public static function fromFieldAndClass(string $fieldName, string $className): self { return new self(sprintf( 'The identifier %s is missing for a query of %s', $fieldName, $className )); } } orm/lib/Doctrine/ORM/Exception/MissingMappingDriverImplementation.php 0000644 00000000627 15120025733 0021774 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; final class MissingMappingDriverImplementation extends ORMException implements ManagerException { public static function create(): self { return new self( "It's a requirement to specify a Metadata Driver and pass it " . 'to Doctrine\\ORM\\Configuration::setMetadataDriverImpl().' ); } } orm/lib/Doctrine/ORM/Exception/MultipleSelectorsFoundException.php 0000644 00000001075 15120025733 0021315 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function implode; use function sprintf; final class MultipleSelectorsFoundException extends ORMException { public const MULTIPLE_SELECTORS_FOUND_EXCEPTION = 'Multiple selectors found: %s. Please select only one.'; /** @param string[] $selectors */ public static function create(array $selectors): self { return new self( sprintf( self::MULTIPLE_SELECTORS_FOUND_EXCEPTION, implode(', ', $selectors) ) ); } } orm/lib/Doctrine/ORM/Exception/NamedNativeQueryNotFound.php 0000644 00000000602 15120025733 0017654 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; final class NamedNativeQueryNotFound extends ORMException implements ConfigurationException { public static function fromName(string $name): self { return new self(sprintf( 'Could not find a named native query by the name "%s"', $name )); } } orm/lib/Doctrine/ORM/Exception/NamedQueryNotFound.php 0000644 00000000565 15120025733 0016515 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; final class NamedQueryNotFound extends ORMException implements ConfigurationException { public static function fromName(string $name): self { return new self(sprintf( 'Could not find a named query by the name "%s"', $name )); } } orm/lib/Doctrine/ORM/Exception/NotSupported.php 0000644 00000002070 15120025733 0015425 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; final class NotSupported extends ORMException { public static function create(): self { return new self('This behaviour is (currently) not supported by Doctrine 2'); } public static function createForDbal3(string $context): self { return new self(sprintf( <<<'EXCEPTION' Context: %s Problem: Feature was deprecated in doctrine/dbal 2.x and is not supported by installed doctrine/dbal:3.x Solution: See the doctrine/deprecations logs for new alternative approaches. EXCEPTION , $context )); } public static function createForPersistence3(string $context): self { return new self(sprintf( <<<'EXCEPTION' Context: %s Problem: Feature was deprecated in doctrine/persistence 2.x and is not supported by installed doctrine/persistence:3.x Solution: See the doctrine/deprecations logs for new alternative approaches. EXCEPTION , $context )); } } orm/lib/Doctrine/ORM/Exception/ORMException.php 0000644 00000000325 15120025733 0015274 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Doctrine\ORM\ORMException as BaseORMException; /** * Should become an interface in 3.0 */ class ORMException extends BaseORMException { } orm/lib/Doctrine/ORM/Exception/PersisterException.php 0000644 00000000313 15120025733 0016614 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Doctrine\ORM\Persisters\PersisterException as BasePersisterException; class PersisterException extends BasePersisterException { } orm/lib/Doctrine/ORM/Exception/ProxyClassesAlwaysRegenerating.php 0000644 00000000443 15120025733 0021134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; final class ProxyClassesAlwaysRegenerating extends ORMException implements ConfigurationException { public static function create(): self { return new self('Proxy Classes are always regenerating.'); } } orm/lib/Doctrine/ORM/Exception/RepositoryException.php 0000644 00000000352 15120025733 0017016 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Throwable; /** * This interface should be implemented by all exceptions in the Repository * namespace. */ interface RepositoryException extends Throwable { } orm/lib/Doctrine/ORM/Exception/SchemaToolException.php 0000644 00000000210 15120025733 0016666 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Throwable; interface SchemaToolException extends Throwable { } orm/lib/Doctrine/ORM/Exception/UnexpectedAssociationValue.php 0000644 00000001075 15120025733 0020261 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use Doctrine\ORM\Cache\Exception\CacheException; use function sprintf; final class UnexpectedAssociationValue extends CacheException { public static function create( string $class, string $association, string $given, string $expected ): self { return new self(sprintf( 'Found entity of type %s on association %s#%s, but expecting %s', $given, $class, $association, $expected )); } } orm/lib/Doctrine/ORM/Exception/UnknownEntityNamespace.php 0000644 00000000704 15120025733 0017432 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function sprintf; /** @deprecated No replacement planned. */ final class UnknownEntityNamespace extends ORMException implements ConfigurationException { public static function fromNamespaceAlias(string $entityNamespaceAlias): self { return new self(sprintf( 'Unknown Entity namespace alias "%s"', $entityNamespaceAlias )); } } orm/lib/Doctrine/ORM/Exception/UnrecognizedIdentifierFields.php 0000644 00000001041 15120025733 0020542 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Exception; use function implode; use function sprintf; final class UnrecognizedIdentifierFields extends ORMException implements ManagerException { /** @param string[] $fieldNames */ public static function fromClassAndFieldNames(string $className, array $fieldNames): self { return new self(sprintf( 'Unrecognized identifier fields: "%s" are not present on class "%s".', implode("', '", $fieldNames), $className )); } } orm/lib/Doctrine/ORM/Id/AbstractIdGenerator.php 0000644 00000004651 15120025733 0015253 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use LogicException; use function get_debug_type; use function sprintf; abstract class AbstractIdGenerator { /** @var bool */ private $alreadyDelegatedToGenerateId = false; /** * Generates an identifier for an entity. * * @deprecated Call {@see generateId()} instead. * * @param object|null $entity * * @return mixed */ public function generate(EntityManager $em, $entity) { if ($this->alreadyDelegatedToGenerateId) { throw new LogicException(sprintf( 'Endless recursion detected in %s. Please implement generateId() without calling the parent implementation.', get_debug_type($this) )); } Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9325', '%s::generate() is deprecated, call generateId() instead.', get_debug_type($this) ); $this->alreadyDelegatedToGenerateId = true; try { return $this->generateId($em, $entity); } finally { $this->alreadyDelegatedToGenerateId = false; } } /** * Generates an identifier for an entity. * * @param object|null $entity * * @return mixed */ public function generateId(EntityManagerInterface $em, $entity) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9325', 'Not implementing %s in %s is deprecated.', __FUNCTION__, get_debug_type($this) ); if (! $em instanceof EntityManager) { throw new InvalidArgumentException('Unsupported entity manager implementation.'); } return $this->generate($em, $entity); } /** * Gets whether this generator is a post-insert generator which means that * {@link generateId()} must be called after the entity has been inserted * into the database. * * By default, this method returns FALSE. Generators that have this requirement * must override this method and return TRUE. * * @return bool */ public function isPostInsertGenerator() { return false; } } orm/lib/Doctrine/ORM/Id/AssignedGenerator.php 0000644 00000002431 15120025733 0014762 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\EntityMissingAssignedId; use function get_class; /** * Special generator for application-assigned identifiers (doesn't really generate anything). */ class AssignedGenerator extends AbstractIdGenerator { /** * Returns the identifier assigned to the given entity. * * {@inheritDoc} * * @throws EntityMissingAssignedId */ public function generateId(EntityManagerInterface $em, $entity) { $class = $em->getClassMetadata(get_class($entity)); $idFields = $class->getIdentifierFieldNames(); $identifier = []; foreach ($idFields as $idField) { $value = $class->getFieldValue($entity, $idField); if (! isset($value)) { throw EntityMissingAssignedId::forField($entity, $idField); } if (isset($class->associationMappings[$idField])) { // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. $value = $em->getUnitOfWork()->getSingleIdentifierValue($value); } $identifier[$idField] = $value; } return $identifier; } } orm/lib/Doctrine/ORM/Id/BigIntegerIdentityGenerator.php 0000644 00000003223 15120025733 0016756 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class BigIntegerIdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string|null */ private $sequenceName; /** * @param string|null $sequenceName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { if ($sequenceName !== null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8850', 'Passing a sequence name to the IdentityGenerator is deprecated in favor of using %s. $sequenceName will be removed in ORM 3.0', SequenceGenerator::class ); } $this->sequenceName = $sequenceName; } /** * {@inheritDoc} */ public function generateId(EntityManagerInterface $em, $entity) { return (string) $em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritDoc} */ public function isPostInsertGenerator() { return true; } } orm/lib/Doctrine/ORM/Id/IdentityGenerator.php 0000644 00000003206 15120025733 0015017 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; /** * Id generator that obtains IDs from special "identity" columns. These are columns * that automatically get a database-generated, auto-incremented identifier on INSERT. * This generator obtains the last insert id after such an insert. */ class IdentityGenerator extends AbstractIdGenerator { /** * The name of the sequence to pass to lastInsertId(), if any. * * @var string|null */ private $sequenceName; /** * @param string|null $sequenceName The name of the sequence to pass to lastInsertId() * to obtain the last generated identifier within the current * database session/connection, if any. */ public function __construct($sequenceName = null) { if ($sequenceName !== null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8850', 'Passing a sequence name to the IdentityGenerator is deprecated in favor of using %s. $sequenceName will be removed in ORM 3.0', SequenceGenerator::class ); } $this->sequenceName = $sequenceName; } /** * {@inheritDoc} */ public function generateId(EntityManagerInterface $em, $entity) { return (int) $em->getConnection()->lastInsertId($this->sequenceName); } /** * {@inheritDoc} */ public function isPostInsertGenerator() { return true; } } orm/lib/Doctrine/ORM/Id/SequenceGenerator.php 0000644 00000005671 15120025733 0015006 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\ORM\EntityManagerInterface; use Serializable; use function serialize; use function unserialize; /** * Represents an ID generator that uses a database sequence. */ class SequenceGenerator extends AbstractIdGenerator implements Serializable { /** * The allocation size of the sequence. * * @var int */ private $allocationSize; /** * The name of the sequence. * * @var string */ private $sequenceName; /** @var int */ private $nextValue = 0; /** @var int|null */ private $maxValue = null; /** * Initializes a new sequence generator. * * @param string $sequenceName The name of the sequence. * @param int $allocationSize The allocation size of the sequence. */ public function __construct($sequenceName, $allocationSize) { $this->sequenceName = $sequenceName; $this->allocationSize = $allocationSize; } /** * {@inheritDoc} */ public function generateId(EntityManagerInterface $em, $entity) { if ($this->maxValue === null || $this->nextValue === $this->maxValue) { // Allocate new values $connection = $em->getConnection(); $sql = $connection->getDatabasePlatform()->getSequenceNextValSQL($this->sequenceName); if ($connection instanceof PrimaryReadReplicaConnection) { $connection->ensureConnectedToPrimary(); } $this->nextValue = (int) $connection->fetchOne($sql); $this->maxValue = $this->nextValue + $this->allocationSize; } return $this->nextValue++; } /** * Gets the maximum value of the currently allocated bag of values. * * @return int|null */ public function getCurrentMaxValue() { return $this->maxValue; } /** * Gets the next value that will be returned by generate(). * * @return int */ public function getNextValue() { return $this->nextValue; } /** * @return string * * @final */ public function serialize() { return serialize($this->__serialize()); } /** @return array<string, mixed> */ public function __serialize(): array { return [ 'allocationSize' => $this->allocationSize, 'sequenceName' => $this->sequenceName, ]; } /** * @param string $serialized * * @return void * * @final */ public function unserialize($serialized) { $this->__unserialize(unserialize($serialized)); } /** @param array<string, mixed> $data */ public function __unserialize(array $data): void { $this->sequenceName = $data['sequenceName']; $this->allocationSize = $data['allocationSize']; } } orm/lib/Doctrine/ORM/Id/TableGenerator.php 0000644 00000004706 15120025733 0014263 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\ORM\EntityManagerInterface; /** * Id generator that uses a single-row database table and a hi/lo algorithm. * * @deprecated no replacement planned */ class TableGenerator extends AbstractIdGenerator { /** @var string */ private $tableName; /** @var string */ private $sequenceName; /** @var int */ private $allocationSize; /** @var int|null */ private $nextValue; /** @var int|null */ private $maxValue; /** * @param string $tableName * @param string $sequenceName * @param int $allocationSize */ public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10) { $this->tableName = $tableName; $this->sequenceName = $sequenceName; $this->allocationSize = $allocationSize; } /** * {@inheritDoc} */ public function generateId( EntityManagerInterface $em, $entity ) { if ($this->maxValue === null || $this->nextValue === $this->maxValue) { // Allocate new values $conn = $em->getConnection(); if ($conn->getTransactionNestingLevel() === 0) { // use select for update $sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->tableName, $this->sequenceName); $currentLevel = $conn->fetchOne($sql); if ($currentLevel !== null) { $this->nextValue = $currentLevel; $this->maxValue = $this->nextValue + $this->allocationSize; $updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql( $this->tableName, $this->sequenceName, $this->allocationSize ); if ($conn->executeStatement($updateSql, [1 => $currentLevel, 2 => $currentLevel + 1]) !== 1) { // no affected rows, concurrency issue, throw exception } } else { // no current level returned, TableGenerator seems to be broken, throw exception } } else { // only table locks help here, implement this or throw exception? // or do we want to work with table locks exclusively? } } return $this->nextValue++; } } orm/lib/Doctrine/ORM/Id/UuidGenerator.php 0000644 00000003026 15120025733 0014134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Id; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\NotSupported; use function method_exists; use function sprintf; /** * Represents an ID generator that uses the database UUID expression * * @deprecated use an application-side generator instead */ class UuidGenerator extends AbstractIdGenerator { public function __construct() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/7312', '%s is deprecated with no replacement, use an application-side generator instead', self::class ); if (! method_exists(AbstractPlatform::class, 'getGuidExpression')) { throw NotSupported::createForDbal3(sprintf( 'Using the database to generate a UUID through %s', self::class )); } } /** * {@inheritDoc} * * @throws NotSupported */ public function generateId(EntityManagerInterface $em, $entity) { $connection = $em->getConnection(); $sql = 'SELECT ' . $connection->getDatabasePlatform()->getGuidExpression(); if ($connection instanceof PrimaryReadReplicaConnection) { $connection->ensureConnectedToPrimary(); } return $connection->executeQuery($sql)->fetchOne(); } } orm/lib/Doctrine/ORM/Internal/CommitOrder/Edge.php 0000644 00000000767 15120025733 0015700 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\CommitOrder; /** @internal */ final class Edge { /** * @var string * @readonly */ public $from; /** * @var string * @readonly */ public $to; /** * @var int * @readonly */ public $weight; public function __construct(string $from, string $to, int $weight) { $this->from = $from; $this->to = $to; $this->weight = $weight; } } orm/lib/Doctrine/ORM/Internal/CommitOrder/Vertex.php 0000644 00000001171 15120025733 0016277 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\CommitOrder; use Doctrine\ORM\Mapping\ClassMetadata; /** @internal */ final class Vertex { /** * @var string * @readonly */ public $hash; /** * @var int * @psalm-var VertexState::* */ public $state = VertexState::NOT_VISITED; /** * @var ClassMetadata * @readonly */ public $value; /** @var array<string, Edge> */ public $dependencyList = []; public function __construct(string $hash, ClassMetadata $value) { $this->hash = $hash; $this->value = $value; } } orm/lib/Doctrine/ORM/Internal/CommitOrder/VertexState.php 0000644 00000000422 15120025733 0017276 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\CommitOrder; /** @internal */ final class VertexState { public const NOT_VISITED = 0; public const IN_PROGRESS = 1; public const VISITED = 2; private function __construct() { } } orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php 0000644 00000056233 15120025733 0020030 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use BackedEnum; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\ForwardCompatibility\Result as ForwardCompatibilityResult; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker; use Doctrine\ORM\UnitOfWork; use Generator; use LogicException; use ReflectionClass; use TypeError; use function array_map; use function array_merge; use function count; use function end; use function get_debug_type; use function in_array; use function is_array; use function sprintf; /** * Base class for all hydrators. A hydrator is a class that provides some form * of transformation of an SQL result set into another structure. */ abstract class AbstractHydrator { /** * The ResultSetMapping. * * @var ResultSetMapping|null */ protected $_rsm; /** * The EntityManager instance. * * @var EntityManagerInterface */ protected $_em; /** * The dbms Platform instance. * * @var AbstractPlatform */ protected $_platform; /** * The UnitOfWork of the associated EntityManager. * * @var UnitOfWork */ protected $_uow; /** * Local ClassMetadata cache to avoid going to the EntityManager all the time. * * @var array<string, ClassMetadata<object>> */ protected $_metadataCache = []; /** * The cache used during row-by-row hydration. * * @var array<string, mixed[]|null> */ protected $_cache = []; /** * The statement that provides the data to hydrate. * * @var Result|null */ protected $_stmt; /** * The query hints. * * @var array<string, mixed> */ protected $_hints = []; /** * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>. * * @param EntityManagerInterface $em The EntityManager to use. */ public function __construct(EntityManagerInterface $em) { $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); $this->_uow = $em->getUnitOfWork(); } /** * Initiates a row-by-row hydration. * * @deprecated * * @param Result|ResultStatement $stmt * @param ResultSetMapping $resultSetMapping * @psalm-param array<string, mixed> $hints * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = []) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8463', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.', __METHOD__ ); $this->_stmt = $stmt instanceof ResultStatement ? ForwardCompatibilityResult::ensure($stmt) : $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $evm = $this->_em->getEventManager(); $evm->addEventListener([Events::onClear], $this); $this->prepare(); return new IterableResult($this); } /** * Initiates a row-by-row hydration. * * @param Result|ResultStatement $stmt * @psalm-param array<string, mixed> $hints * * @return Generator<array-key, mixed> * * @final */ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable { if (! $stmt instanceof Result) { if (! $stmt instanceof ResultStatement) { throw new TypeError(sprintf( '%s: Expected parameter $stmt to be an instance of %s or %s, got %s', __METHOD__, Result::class, ResultStatement::class, get_debug_type($stmt) )); } Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/8796', '%s: Passing a result as $stmt that does not implement %s is deprecated and will cause a TypeError on 3.0', __METHOD__, Result::class ); $stmt = ForwardCompatibilityResult::ensure($stmt); } $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $evm = $this->_em->getEventManager(); $evm->addEventListener([Events::onClear], $this); $this->prepare(); while (true) { $row = $this->statement()->fetchAssociative(); if ($row === false) { $this->cleanup(); break; } $result = []; $this->hydrateRowData($row, $result); $this->cleanupAfterRowIteration(); if (count($result) === 1) { if (count($resultSetMapping->indexByMap) === 0) { yield end($result); } else { yield from $result; } } else { yield $result; } } } final protected function statement(): Result { if ($this->_stmt === null) { throw new LogicException('Uninitialized _stmt property'); } return $this->_stmt; } final protected function resultSetMapping(): ResultSetMapping { if ($this->_rsm === null) { throw new LogicException('Uninitialized _rsm property'); } return $this->_rsm; } /** * Hydrates all rows returned by the passed statement instance at once. * * @param Result|ResultStatement $stmt * @param ResultSetMapping $resultSetMapping * @psalm-param array<string, string> $hints * * @return mixed[] */ public function hydrateAll($stmt, $resultSetMapping, array $hints = []) { if (! $stmt instanceof Result) { if (! $stmt instanceof ResultStatement) { throw new TypeError(sprintf( '%s: Expected parameter $stmt to be an instance of %s or %s, got %s', __METHOD__, Result::class, ResultStatement::class, get_debug_type($stmt) )); } Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/8796', '%s: Passing a result as $stmt that does not implement %s is deprecated and will cause a TypeError on 3.0', __METHOD__, Result::class ); $stmt = ForwardCompatibilityResult::ensure($stmt); } $this->_stmt = $stmt; $this->_rsm = $resultSetMapping; $this->_hints = $hints; $this->_em->getEventManager()->addEventListener([Events::onClear], $this); $this->prepare(); try { $result = $this->hydrateAllData(); } finally { $this->cleanup(); } return $result; } /** * Hydrates a single row returned by the current statement instance during * row-by-row hydration with {@link iterate()} or {@link toIterable()}. * * @deprecated * * @return mixed[]|false */ public function hydrateRow() { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9072', '%s is deprecated.', __METHOD__ ); $row = $this->statement()->fetchAssociative(); if ($row === false) { $this->cleanup(); return false; } $result = []; $this->hydrateRowData($row, $result); return $result; } /** * When executed in a hydrate() loop we have to clear internal state to * decrease memory consumption. * * @param mixed $eventArgs * * @return void */ public function onClear($eventArgs) { } /** * Executes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. * * @return void */ protected function prepare() { } /** * Executes one-time cleanup tasks at the end of a hydration that was initiated * through {@link hydrateAll} or {@link iterate()}. * * @return void */ protected function cleanup() { $this->statement()->free(); $this->_stmt = null; $this->_rsm = null; $this->_cache = []; $this->_metadataCache = []; $this ->_em ->getEventManager() ->removeEventListener([Events::onClear], $this); } protected function cleanupAfterRowIteration(): void { } /** * Hydrates a single row from the current statement instance. * * Template method. * * @param mixed[] $row The row data. * @param mixed[] $result The result to fill. * * @return void * * @throws HydrationException */ protected function hydrateRowData(array $row, array &$result) { throw new HydrationException('hydrateRowData() not implemented by this hydrator.'); } /** * Hydrates all rows from the current statement instance at once. * * @return mixed[] */ abstract protected function hydrateAllData(); /** * Processes a row of the result set. * * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). * Puts the elements of a result row into a new array, grouped by the dql alias * they belong to. The column names in the result set are mapped to their * field names during this procedure as well as any necessary conversions on * the values applied. Scalar values are kept in a specific key 'scalars'. * * @param mixed[] $data SQL Result Row. * @psalm-param array<string, string> $id Dql-Alias => ID-Hash. * @psalm-param array<string, bool> $nonemptyComponents Does this DQL-Alias has at least one non NULL value? * * @return array<string, array<string, mixed>> An array with all the fields * (name => value) of the data * row, grouped by their * component alias. * @psalm-return array{ * data: array<array-key, array>, * newObjects?: array<array-key, array{ * class: mixed, * args?: array * }>, * scalars?: array * } */ protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents) { $rowData = ['data' => []]; foreach ($data as $key => $value) { $cacheKeyInfo = $this->hydrateColumnInfo($key); if ($cacheKeyInfo === null) { continue; } $fieldName = $cacheKeyInfo['fieldName']; switch (true) { case isset($cacheKeyInfo['isNewObjectParameter']): $argIndex = $cacheKeyInfo['argIndex']; $objIndex = $cacheKeyInfo['objIndex']; $type = $cacheKeyInfo['type']; $value = $type->convertToPHPValue($value, $this->_platform); if ($value !== null && isset($cacheKeyInfo['enumType'])) { $value = $this->buildEnum($value, $cacheKeyInfo['enumType']); } $rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class']; $rowData['newObjects'][$objIndex]['args'][$argIndex] = $value; break; case isset($cacheKeyInfo['isScalar']): $type = $cacheKeyInfo['type']; $value = $type->convertToPHPValue($value, $this->_platform); if ($value !== null && isset($cacheKeyInfo['enumType'])) { $value = $this->buildEnum($value, $cacheKeyInfo['enumType']); } $rowData['scalars'][$fieldName] = $value; break; //case (isset($cacheKeyInfo['isMetaColumn'])): default: $dqlAlias = $cacheKeyInfo['dqlAlias']; $type = $cacheKeyInfo['type']; // If there are field name collisions in the child class, then we need // to only hydrate if we are looking at the correct discriminator value if ( isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']]) && ! in_array((string) $data[$cacheKeyInfo['discriminatorColumn']], $cacheKeyInfo['discriminatorValues'], true) ) { break; } // in an inheritance hierarchy the same field could be defined several times. // We overwrite this value so long we don't have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. if (isset($rowData['data'][$dqlAlias][$fieldName])) { break; } $rowData['data'][$dqlAlias][$fieldName] = $type ? $type->convertToPHPValue($value, $this->_platform) : $value; if ($rowData['data'][$dqlAlias][$fieldName] !== null && isset($cacheKeyInfo['enumType'])) { $rowData['data'][$dqlAlias][$fieldName] = $this->buildEnum($rowData['data'][$dqlAlias][$fieldName], $cacheKeyInfo['enumType']); } if ($cacheKeyInfo['isIdentifier'] && $value !== null) { $id[$dqlAlias] .= '|' . $value; $nonemptyComponents[$dqlAlias] = true; } break; } } return $rowData; } /** * Processes a row of the result set. * * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that * simply converts column names to field names and properly converts the * values according to their types. The resulting row has the same number * of elements as before. * * @param mixed[] $data * @psalm-param array<string, mixed> $data * * @return mixed[] The processed row. * @psalm-return array<string, mixed> */ protected function gatherScalarRowData(&$data) { $rowData = []; foreach ($data as $key => $value) { $cacheKeyInfo = $this->hydrateColumnInfo($key); if ($cacheKeyInfo === null) { continue; } $fieldName = $cacheKeyInfo['fieldName']; // WARNING: BC break! We know this is the desired behavior to type convert values, but this // erroneous behavior exists since 2.0 and we're forced to keep compatibility. if (! isset($cacheKeyInfo['isScalar'])) { $type = $cacheKeyInfo['type']; $value = $type ? $type->convertToPHPValue($value, $this->_platform) : $value; $fieldName = $cacheKeyInfo['dqlAlias'] . '_' . $fieldName; } $rowData[$fieldName] = $value; } return $rowData; } /** * Retrieve column information from ResultSetMapping. * * @param string $key Column name * * @return mixed[]|null * @psalm-return array<string, mixed>|null */ protected function hydrateColumnInfo($key) { if (isset($this->_cache[$key])) { return $this->_cache[$key]; } switch (true) { // NOTE: Most of the times it's a field mapping, so keep it first!!! case isset($this->_rsm->fieldMappings[$key]): $classMetadata = $this->getClassMetadata($this->_rsm->declaringClasses[$key]); $fieldName = $this->_rsm->fieldMappings[$key]; $fieldMapping = $classMetadata->fieldMappings[$fieldName]; $ownerMap = $this->_rsm->columnOwnerMap[$key]; $columnInfo = [ 'isIdentifier' => in_array($fieldName, $classMetadata->identifier, true), 'fieldName' => $fieldName, 'type' => Type::getType($fieldMapping['type']), 'dqlAlias' => $ownerMap, 'enumType' => $this->_rsm->enumMappings[$key] ?? null, ]; // the current discriminator value must be saved in order to disambiguate fields hydration, // should there be field name collisions if ($classMetadata->parentClasses && isset($this->_rsm->discriminatorColumns[$ownerMap])) { return $this->_cache[$key] = array_merge( $columnInfo, [ 'discriminatorColumn' => $this->_rsm->discriminatorColumns[$ownerMap], 'discriminatorValue' => $classMetadata->discriminatorValue, 'discriminatorValues' => $this->getDiscriminatorValues($classMetadata), ] ); } return $this->_cache[$key] = $columnInfo; case isset($this->_rsm->newObjectMappings[$key]): // WARNING: A NEW object is also a scalar, so it must be declared before! $mapping = $this->_rsm->newObjectMappings[$key]; return $this->_cache[$key] = [ 'isScalar' => true, 'isNewObjectParameter' => true, 'fieldName' => $this->_rsm->scalarMappings[$key], 'type' => Type::getType($this->_rsm->typeMappings[$key]), 'argIndex' => $mapping['argIndex'], 'objIndex' => $mapping['objIndex'], 'class' => new ReflectionClass($mapping['className']), 'enumType' => $this->_rsm->enumMappings[$key] ?? null, ]; case isset($this->_rsm->scalarMappings[$key], $this->_hints[LimitSubqueryWalker::FORCE_DBAL_TYPE_CONVERSION]): return $this->_cache[$key] = [ 'fieldName' => $this->_rsm->scalarMappings[$key], 'type' => Type::getType($this->_rsm->typeMappings[$key]), 'dqlAlias' => '', 'enumType' => $this->_rsm->enumMappings[$key] ?? null, ]; case isset($this->_rsm->scalarMappings[$key]): return $this->_cache[$key] = [ 'isScalar' => true, 'fieldName' => $this->_rsm->scalarMappings[$key], 'type' => Type::getType($this->_rsm->typeMappings[$key]), 'enumType' => $this->_rsm->enumMappings[$key] ?? null, ]; case isset($this->_rsm->metaMappings[$key]): // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). $fieldName = $this->_rsm->metaMappings[$key]; $dqlAlias = $this->_rsm->columnOwnerMap[$key]; $type = isset($this->_rsm->typeMappings[$key]) ? Type::getType($this->_rsm->typeMappings[$key]) : null; // Cache metadata fetch $this->getClassMetadata($this->_rsm->aliasMap[$dqlAlias]); return $this->_cache[$key] = [ 'isIdentifier' => isset($this->_rsm->isIdentifierColumn[$dqlAlias][$key]), 'isMetaColumn' => true, 'fieldName' => $fieldName, 'type' => $type, 'dqlAlias' => $dqlAlias, 'enumType' => $this->_rsm->enumMappings[$key] ?? null, ]; } // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. return null; } /** * @return string[] * @psalm-return non-empty-list<string> */ private function getDiscriminatorValues(ClassMetadata $classMetadata): array { $values = array_map( function (string $subClass): string { return (string) $this->getClassMetadata($subClass)->discriminatorValue; }, $classMetadata->subClasses ); $values[] = (string) $classMetadata->discriminatorValue; return $values; } /** * Retrieve ClassMetadata associated to entity class name. * * @param string $className * * @return ClassMetadata */ protected function getClassMetadata($className) { if (! isset($this->_metadataCache[$className])) { $this->_metadataCache[$className] = $this->_em->getClassMetadata($className); } return $this->_metadataCache[$className]; } /** * Register entity as managed in UnitOfWork. * * @param object $entity * @param mixed[] $data * * @return void * * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow */ protected function registerManaged(ClassMetadata $class, $entity, array $data) { if ($class->isIdentifierComposite) { $id = []; foreach ($class->identifier as $fieldName) { $id[$fieldName] = isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName]; } } else { $fieldName = $class->identifier[0]; $id = [ $fieldName => isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName], ]; } $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } /** * @param mixed $value * @param class-string<BackedEnum> $enumType * * @return BackedEnum|array<BackedEnum> */ final protected function buildEnum($value, string $enumType) { if (is_array($value)) { return array_map(static function ($value) use ($enumType): BackedEnum { return $enumType::from($value); }, $value); } return $enumType::from($value); } } orm/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php 0000644 00000023234 15120025733 0017336 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\Mapping\ClassMetadata; use function count; use function end; use function is_array; use function key; use function reset; /** * The ArrayHydrator produces a nested array "graph" that is often (not always) * interchangeable with the corresponding object graph for read-only access. */ class ArrayHydrator extends AbstractHydrator { /** @var array<string,bool> */ private $rootAliases = []; /** @var bool */ private $isSimpleQuery = false; /** @var mixed[] */ private $identifierMap = []; /** @var mixed[] */ private $resultPointers = []; /** @var array<string,string> */ private $idTemplate = []; /** @var int */ private $resultCounter = 0; /** * {@inheritDoc} */ protected function prepare() { $this->isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1; foreach ($this->resultSetMapping()->aliasMap as $dqlAlias => $className) { $this->identifierMap[$dqlAlias] = []; $this->resultPointers[$dqlAlias] = []; $this->idTemplate[$dqlAlias] = ''; } } /** * {@inheritDoc} */ protected function hydrateAllData() { $result = []; while ($data = $this->statement()->fetchAssociative()) { $this->hydrateRowData($data, $result); } return $result; } /** * {@inheritDoc} */ protected function hydrateRowData(array $row, array &$result) { // 1) Initialize $id = $this->idTemplate; // initialize the id-memory $nonemptyComponents = []; $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); // 2) Now hydrate the data found in the current row. foreach ($rowData['data'] as $dqlAlias => $data) { $index = false; if (isset($this->resultSetMapping()->parentAliasMap[$dqlAlias])) { // It's a joined result $parent = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; $path = $parent . '.' . $dqlAlias; // missing parent data, skipping as RIGHT JOIN hydration is not supported. if (! isset($nonemptyComponents[$parent])) { continue; } // Get a reference to the right element in the result tree. // This element will get the associated element attached. if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parent])) { $first = reset($this->resultPointers); // TODO: Exception if $key === null ? $baseElement =& $this->resultPointers[$parent][key($first)]; } elseif (isset($this->resultPointers[$parent])) { $baseElement =& $this->resultPointers[$parent]; } else { unset($this->resultPointers[$dqlAlias]); // Ticket #1228 continue; } $relationAlias = $this->resultSetMapping()->relationMap[$dqlAlias]; $parentClass = $this->_metadataCache[$this->resultSetMapping()->aliasMap[$parent]]; $relation = $parentClass->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if (! ($relation['type'] & ClassMetadata::TO_ONE)) { $oneToOne = false; if (! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = []; } if (isset($nonemptyComponents[$dqlAlias])) { $indexExists = isset($this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $index = $indexExists ? $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false; if (! $indexExists || ! $indexIsValid) { $element = $data; if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { $baseElement[$relationAlias][$row[$this->resultSetMapping()->indexByMap[$dqlAlias]]] = $element; } else { $baseElement[$relationAlias][] = $element; } end($baseElement[$relationAlias]); $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]); } } } else { $oneToOne = true; if ( ! isset($nonemptyComponents[$dqlAlias]) && ( ! isset($baseElement[$relationAlias])) ) { $baseElement[$relationAlias] = null; } elseif (! isset($baseElement[$relationAlias])) { $baseElement[$relationAlias] = $data; } } $coll =& $baseElement[$relationAlias]; if (is_array($coll)) { $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne); } } else { // It's a root result element $this->rootAliases[$dqlAlias] = true; // Mark as root $entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if (! isset($nonemptyComponents[$dqlAlias])) { $result[] = $this->resultSetMapping()->isMixed ? [$entityKey => null] : null; $resultKey = $this->resultCounter; ++$this->resultCounter; continue; } // Check for an existing element if ($this->isSimpleQuery || ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->resultSetMapping()->isMixed ? [$entityKey => $data] : $data; if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { $resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; $result[$resultKey] = $element; } else { $resultKey = $this->resultCounter; $result[] = $element; ++$this->resultCounter; } $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; } else { $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; $resultKey = $index; } $this->updateResultPointer($result, $index, $dqlAlias, false); } } if (! isset($resultKey)) { $this->resultCounter++; } // Append scalar values to mixed result sets if (isset($rowData['scalars'])) { if (! isset($resultKey)) { // this only ever happens when no object is fetched (scalar result only) $resultKey = isset($this->resultSetMapping()->indexByMap['scalars']) ? $row[$this->resultSetMapping()->indexByMap['scalars']] : $this->resultCounter - 1; } foreach ($rowData['scalars'] as $name => $value) { $result[$resultKey][$name] = $value; } } // Append new object to mixed result sets if (isset($rowData['newObjects'])) { if (! isset($resultKey)) { $resultKey = $this->resultCounter - 1; } $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0); foreach ($rowData['newObjects'] as $objIndex => $newObject) { $class = $newObject['class']; $args = $newObject['args']; $obj = $class->newInstanceArgs($args); if (count($args) === $scalarCount || ($scalarCount === 0 && count($rowData['newObjects']) === 1)) { $result[$resultKey] = $obj; continue; } $result[$resultKey][$objIndex] = $obj; } } } /** * Updates the result pointer for an Entity. The result pointers point to the * last seen instance of each Entity type. This is used for graph construction. * * @param mixed[]|null $coll The element. * @param bool|int $index Index of the element in the collection. * @param bool $oneToOne Whether it is a single-valued association or not. */ private function updateResultPointer( ?array &$coll, $index, string $dqlAlias, bool $oneToOne ): void { if ($coll === null) { unset($this->resultPointers[$dqlAlias]); // Ticket #1228 return; } if ($oneToOne) { $this->resultPointers[$dqlAlias] =& $coll; return; } if ($index !== false) { $this->resultPointers[$dqlAlias] =& $coll[$index]; return; } if (! $coll) { return; } end($coll); $this->resultPointers[$dqlAlias] =& $coll[key($coll)]; } } orm/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php 0000644 00000005213 15120025733 0020360 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\Exception\ORMException; use function implode; use function sprintf; class HydrationException extends ORMException { /** @return HydrationException */ public static function nonUniqueResult() { return new self('The result returned by the query was not unique.'); } /** * @param string $alias * @param string $parentAlias * * @return HydrationException */ public static function parentObjectOfRelationNotFound($alias, $parentAlias) { return new self(sprintf( "The parent object of entity result with alias '%s' was not found." . " The parent alias is '%s'.", $alias, $parentAlias )); } /** * @param string $dqlAlias * * @return HydrationException */ public static function emptyDiscriminatorValue($dqlAlias) { return new self("The DQL alias '" . $dqlAlias . "' contains an entity " . 'of an inheritance hierarchy with an empty discriminator value. This means ' . 'that the database contains inconsistent data with an empty ' . 'discriminator value in a table row.'); } /** * @param string $entityName * @param string $discrColumnName * @param string $dqlAlias * * @return HydrationException */ public static function missingDiscriminatorColumn($entityName, $discrColumnName, $dqlAlias) { return new self(sprintf( 'The discriminator column "%s" is missing for "%s" using the DQL alias "%s".', $discrColumnName, $entityName, $dqlAlias )); } /** * @param string $entityName * @param string $discrColumnName * @param string $dqlAlias * * @return HydrationException */ public static function missingDiscriminatorMetaMappingColumn($entityName, $discrColumnName, $dqlAlias) { return new self(sprintf( 'The meta mapping for the discriminator column "%s" is missing for "%s" using the DQL alias "%s".', $discrColumnName, $entityName, $dqlAlias )); } /** * @param string $discrValue * @param list<int|string> $discrValues * * @return HydrationException */ public static function invalidDiscriminatorValue($discrValue, $discrValues) { return new self(sprintf( 'The discriminator value "%s" is invalid. It must be one of "%s".', $discrValue, implode('", "', $discrValues) )); } } orm/lib/Doctrine/ORM/Internal/Hydration/IterableResult.php 0000644 00000003243 15120025733 0017467 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Iterator; use ReturnTypeWillChange; /** * Represents a result structure that can be iterated over, hydrating row-by-row * during the iteration. An IterableResult is obtained by AbstractHydrator#iterate(). * * @deprecated */ class IterableResult implements Iterator { /** @var AbstractHydrator */ private $hydrator; /** @var bool */ private $rewinded = false; /** @var int */ private $key = -1; /** @var mixed[]|null */ private $current = null; /** @param AbstractHydrator $hydrator */ public function __construct($hydrator) { $this->hydrator = $hydrator; } /** * @return void * * @throws HydrationException */ #[ReturnTypeWillChange] public function rewind() { if ($this->rewinded === true) { throw new HydrationException('Can only iterate a Result once.'); } $this->current = $this->next(); $this->rewinded = true; } /** * Gets the next set of results. * * @return mixed[]|false */ #[ReturnTypeWillChange] public function next() { $this->current = $this->hydrator->hydrateRow(); $this->key++; return $this->current; } /** @return mixed */ #[ReturnTypeWillChange] public function current() { return $this->current; } /** @return int */ #[ReturnTypeWillChange] public function key() { return $this->key; } /** @return bool */ #[ReturnTypeWillChange] public function valid() { return $this->current !== false; } } orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php 0000644 00000061532 15120025733 0017471 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use BackedEnum; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; use Doctrine\ORM\UnitOfWork; use Doctrine\Persistence\Proxy; use function array_fill_keys; use function array_keys; use function array_map; use function count; use function is_array; use function key; use function ltrim; use function spl_object_id; /** * The ObjectHydrator constructs an object graph out of an SQL result set. * * Internal note: Highly performance-sensitive code. */ class ObjectHydrator extends AbstractHydrator { /** @var mixed[] */ private $identifierMap = []; /** @var mixed[] */ private $resultPointers = []; /** @var mixed[] */ private $idTemplate = []; /** @var int */ private $resultCounter = 0; /** @var mixed[] */ private $rootAliases = []; /** @var mixed[] */ private $initializedCollections = []; /** @var array<string, PersistentCollection> */ private $uninitializedCollections = []; /** @var mixed[] */ private $existingCollections = []; /** * {@inheritDoc} */ protected function prepare() { if (! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) { $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true; } foreach ($this->resultSetMapping()->aliasMap as $dqlAlias => $className) { $this->identifierMap[$dqlAlias] = []; $this->idTemplate[$dqlAlias] = ''; // Remember which associations are "fetch joined", so that we know where to inject // collection stubs or proxies and where not. if (! isset($this->resultSetMapping()->relationMap[$dqlAlias])) { continue; } $parent = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; if (! isset($this->resultSetMapping()->aliasMap[$parent])) { throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent); } $sourceClassName = $this->resultSetMapping()->aliasMap[$parent]; $sourceClass = $this->getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->resultSetMapping()->relationMap[$dqlAlias]]; $this->_hints['fetched'][$parent][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { continue; } // Mark any non-collection opposite sides as fetched, too. if ($assoc['mappedBy']) { $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true; continue; } // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { $class = $this->getClassMetadata($className); $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; if (! ($inverseAssoc['type'] & ClassMetadata::TO_ONE)) { continue; } $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true; } } } /** * {@inheritDoc} */ protected function cleanup() { $eagerLoad = isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD]) && $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] === true; parent::cleanup(); $this->identifierMap = $this->initializedCollections = $this->uninitializedCollections = $this->existingCollections = $this->resultPointers = []; if ($eagerLoad) { $this->_uow->triggerEagerLoads(); } $this->_uow->hydrationComplete(); } protected function cleanupAfterRowIteration(): void { $this->identifierMap = $this->initializedCollections = $this->uninitializedCollections = $this->existingCollections = $this->resultPointers = []; } /** * {@inheritDoc} */ protected function hydrateAllData() { $result = []; while ($row = $this->statement()->fetchAssociative()) { $this->hydrateRowData($row, $result); } // Take snapshots from all newly initialized collections foreach ($this->initializedCollections as $coll) { $coll->takeSnapshot(); } foreach ($this->uninitializedCollections as $coll) { if (! $coll->isInitialized()) { $coll->setInitialized(true); } } return $result; } /** * Initializes a related collection. * * @param object $entity The entity to which the collection belongs. * @param string $fieldName The name of the field on the entity that holds the collection. * @param string $parentDqlAlias Alias of the parent fetch joining this collection. */ private function initRelatedCollection( $entity, ClassMetadata $class, string $fieldName, string $parentDqlAlias ): PersistentCollection { $oid = spl_object_id($entity); $relation = $class->associationMappings[$fieldName]; $value = $class->reflFields[$fieldName]->getValue($entity); if ($value === null || is_array($value)) { $value = new ArrayCollection((array) $value); } if (! $value instanceof PersistentCollection) { $value = new PersistentCollection( $this->_em, $this->_metadataCache[$relation['targetEntity']], $value ); $value->setOwner($entity, $relation); $class->reflFields[$fieldName]->setValue($entity, $value); $this->_uow->setOriginalEntityProperty($oid, $fieldName, $value); $this->initializedCollections[$oid . $fieldName] = $value; } elseif ( isset($this->_hints[Query::HINT_REFRESH]) || isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && ! $value->isInitialized() ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! $value->setDirty(false); $value->setInitialized(true); $value->unwrap()->clear(); $this->initializedCollections[$oid . $fieldName] = $value; } else { // Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN! $this->existingCollections[$oid . $fieldName] = $value; } return $value; } /** * Gets an entity instance. * * @param string $dqlAlias The DQL alias of the entity's class. * @psalm-param array<string, mixed> $data The instance data. * * @return object * * @throws HydrationException */ private function getEntity(array $data, string $dqlAlias) { $className = $this->resultSetMapping()->aliasMap[$dqlAlias]; if (isset($this->resultSetMapping()->discriminatorColumns[$dqlAlias])) { $fieldName = $this->resultSetMapping()->discriminatorColumns[$dqlAlias]; if (! isset($this->resultSetMapping()->metaMappings[$fieldName])) { throw HydrationException::missingDiscriminatorMetaMappingColumn($className, $fieldName, $dqlAlias); } $discrColumn = $this->resultSetMapping()->metaMappings[$fieldName]; if (! isset($data[$discrColumn])) { throw HydrationException::missingDiscriminatorColumn($className, $discrColumn, $dqlAlias); } if ($data[$discrColumn] === '') { throw HydrationException::emptyDiscriminatorValue($dqlAlias); } $discrMap = $this->_metadataCache[$className]->discriminatorMap; $discriminatorValue = $data[$discrColumn]; if ($discriminatorValue instanceof BackedEnum) { $discriminatorValue = $discriminatorValue->value; } $discriminatorValue = (string) $discriminatorValue; if (! isset($discrMap[$discriminatorValue])) { throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap)); } $className = $discrMap[$discriminatorValue]; unset($data[$discrColumn]); } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY], $this->rootAliases[$dqlAlias])) { $this->registerManaged($this->_metadataCache[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $this->_hints['fetchAlias'] = $dqlAlias; return $this->_uow->createEntity($className, $data, $this->_hints); } /** * @psalm-param class-string $className * @psalm-param array<string, mixed> $data * * @return mixed */ private function getEntityFromIdentityMap(string $className, array $data) { // TODO: Abstract this code and UnitOfWork::createEntity() equivalent? $class = $this->_metadataCache[$className]; if ($class->isIdentifierComposite) { $idHash = UnitOfWork::getIdHashByIdentifier( array_map( /** @return mixed */ static function (string $fieldName) use ($data, $class) { return isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName]; }, $class->identifier ) ); return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName); } elseif (isset($class->associationMappings[$class->identifier[0]])) { return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName); } return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName); } /** * Hydrates a single row in an SQL result set. * * @internal * First, the data of the row is split into chunks where each chunk contains data * that belongs to a particular component/class. Afterwards, all these chunks * are processed, one after the other. For each chunk of class data only one of the * following code paths is executed: * * Path A: The data chunk belongs to a joined/associated object and the association * is collection-valued. * Path B: The data chunk belongs to a joined/associated object and the association * is single-valued. * Path C: The data chunk belongs to a root result element/object that appears in the topmost * level of the hydrated result. A typical example are the objects of the type * specified by the FROM clause in a DQL query. * * @param mixed[] $row The data of the row to process. * @param mixed[] $result The result array to fill. * * @return void */ protected function hydrateRowData(array $row, array &$result) { // Initialize $id = $this->idTemplate; // initialize the id-memory $nonemptyComponents = []; // Split the row data into chunks of class data. $rowData = $this->gatherRowData($row, $id, $nonemptyComponents); // reset result pointers for each data row $this->resultPointers = []; // Hydrate the data chunks foreach ($rowData['data'] as $dqlAlias => $data) { $entityName = $this->resultSetMapping()->aliasMap[$dqlAlias]; if (isset($this->resultSetMapping()->parentAliasMap[$dqlAlias])) { // It's a joined result $parentAlias = $this->resultSetMapping()->parentAliasMap[$dqlAlias]; // we need the $path to save into the identifier map which entities were already // seen for this parent-child relationship $path = $parentAlias . '.' . $dqlAlias; // We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs if (! isset($nonemptyComponents[$parentAlias])) { // TODO: Add special case code where we hydrate the right join objects into identity map at least continue; } $parentClass = $this->_metadataCache[$this->resultSetMapping()->aliasMap[$parentAlias]]; $relationField = $this->resultSetMapping()->relationMap[$dqlAlias]; $relation = $parentClass->associationMappings[$relationField]; $reflField = $parentClass->reflFields[$relationField]; // Get a reference to the parent object to which the joined element belongs. if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parentAlias])) { $objectClass = $this->resultPointers[$parentAlias]; $parentObject = $objectClass[key($objectClass)]; } elseif (isset($this->resultPointers[$parentAlias])) { $parentObject = $this->resultPointers[$parentAlias]; } else { // Parent object of relation not found, mark as not-fetched again $element = $this->getEntity($data, $dqlAlias); // Update result pointer and provide initial fetch data for parent $this->resultPointers[$dqlAlias] = $element; $rowData['data'][$parentAlias][$relationField] = $element; // Mark as not-fetched again unset($this->_hints['fetched'][$parentAlias][$relationField]); continue; } $oid = spl_object_id($parentObject); // Check the type of the relation (many or single-valued) if (! ($relation['type'] & ClassMetadata::TO_ONE)) { // PATH A: Collection-valued association $reflFieldValue = $reflField->getValue($parentObject); if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; if (isset($this->initializedCollections[$collKey])) { $reflFieldValue = $this->initializedCollections[$collKey]; } elseif (! isset($this->existingCollections[$collKey])) { $reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } $indexExists = isset($this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); $index = $indexExists ? $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false; $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; if (! $indexExists || ! $indexIsValid) { if (isset($this->existingCollections[$collKey])) { // Collection exists, only look for the element in the identity map. $element = $this->getEntityFromIdentityMap($entityName, $data); if ($element) { $this->resultPointers[$dqlAlias] = $element; } else { unset($this->resultPointers[$dqlAlias]); } } else { $element = $this->getEntity($data, $dqlAlias); if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { $indexValue = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; $reflFieldValue->hydrateSet($indexValue, $element); $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue; } else { if (! $reflFieldValue->contains($element)) { $reflFieldValue->hydrateAdd($element); $reflFieldValue->last(); } $this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key(); } // Update result pointer $this->resultPointers[$dqlAlias] = $element; } } else { // Update result pointer $this->resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } elseif (! $reflFieldValue) { $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } elseif ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false && ! isset($this->uninitializedCollections[$oid . $relationField])) { $this->uninitializedCollections[$oid . $relationField] = $reflFieldValue; } } else { // PATH B: Single-valued association $reflFieldValue = $reflField->getValue($parentObject); if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized())) { // we only need to take action if this value is null, // we refresh the entity or its an uninitialized proxy. if (isset($nonemptyComponents[$dqlAlias])) { $element = $this->getEntity($data, $dqlAlias); $reflField->setValue($parentObject, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $targetClass = $this->_metadataCache[$relation['targetEntity']]; if ($relation['isOwningSide']) { // TODO: Just check hints['fetched'] here? // If there is an inverse mapping on the target class its bidirectional if ($relation['inversedBy']) { $inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']]; if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) { $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_id($element), $inverseAssoc['fieldName'], $parentObject); } } elseif ($parentClass === $targetClass && $relation['mappedBy']) { // Special case: bi-directional self-referencing one-one on the same class $targetClass->reflFields[$relationField]->setValue($element, $parentObject); } } else { // For sure bidirectional, as there is no inverse side in unidirectional mappings $targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject); $this->_uow->setOriginalEntityProperty(spl_object_id($element), $relation['mappedBy'], $parentObject); } // Update result pointer $this->resultPointers[$dqlAlias] = $element; } else { $this->_uow->setOriginalEntityProperty($oid, $relationField, null); $reflField->setValue($parentObject, null); } // else leave $reflFieldValue null for single-valued associations } else { // Update result pointer $this->resultPointers[$dqlAlias] = $reflFieldValue; } } } else { // PATH C: Its a root result element $this->rootAliases[$dqlAlias] = true; // Mark as root alias $entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0; // if this row has a NULL value for the root result id then make it a null result. if (! isset($nonemptyComponents[$dqlAlias])) { if ($this->resultSetMapping()->isMixed) { $result[] = [$entityKey => null]; } else { $result[] = null; } $resultKey = $this->resultCounter; ++$this->resultCounter; continue; } // check for existing result from the iterations before if (! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) { $element = $this->getEntity($data, $dqlAlias); if ($this->resultSetMapping()->isMixed) { $element = [$entityKey => $element]; } if (isset($this->resultSetMapping()->indexByMap[$dqlAlias])) { $resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]]; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateSet($resultKey, $element); } $result[$resultKey] = $element; } else { $resultKey = $this->resultCounter; ++$this->resultCounter; if (isset($this->_hints['collection'])) { $this->_hints['collection']->hydrateAdd($element); } $result[] = $element; } $this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey; // Update result pointer $this->resultPointers[$dqlAlias] = $element; } else { // Update result pointer $index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]]; $this->resultPointers[$dqlAlias] = $result[$index]; $resultKey = $index; } } if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) { $this->_uow->hydrationComplete(); } } if (! isset($resultKey)) { $this->resultCounter++; } // Append scalar values to mixed result sets if (isset($rowData['scalars'])) { if (! isset($resultKey)) { $resultKey = isset($this->resultSetMapping()->indexByMap['scalars']) ? $row[$this->resultSetMapping()->indexByMap['scalars']] : $this->resultCounter - 1; } foreach ($rowData['scalars'] as $name => $value) { $result[$resultKey][$name] = $value; } } // Append new object to mixed result sets if (isset($rowData['newObjects'])) { if (! isset($resultKey)) { $resultKey = $this->resultCounter - 1; } $scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0); foreach ($rowData['newObjects'] as $objIndex => $newObject) { $class = $newObject['class']; $args = $newObject['args']; $obj = $class->newInstanceArgs($args); if ($scalarCount === 0 && count($rowData['newObjects']) === 1) { $result[$resultKey] = $obj; continue; } $result[$resultKey][$objIndex] = $obj; } } } /** * When executed in a hydrate() loop we may have to clear internal state to * decrease memory consumption. * * @param mixed $eventArgs * * @return void */ public function onClear($eventArgs) { parent::onClear($eventArgs); $aliases = array_keys($this->identifierMap); $this->identifierMap = array_fill_keys($aliases, []); } } orm/lib/Doctrine/ORM/Internal/Hydration/ScalarColumnHydrator.php 0000644 00000001432 15120025733 0020637 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Doctrine\DBAL\Driver\Exception; use Doctrine\ORM\Exception\MultipleSelectorsFoundException; use function array_column; use function count; /** * Hydrator that produces one-dimensional array. */ final class ScalarColumnHydrator extends AbstractHydrator { /** * {@inheritDoc} * * @throws MultipleSelectorsFoundException * @throws Exception */ protected function hydrateAllData(): array { if (count($this->resultSetMapping()->fieldMappings) > 1) { throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings); } $result = $this->statement()->fetchAllNumeric(); return array_column($result, 0); } } orm/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php 0000644 00000001436 15120025733 0017465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; /** * Hydrator that produces flat, rectangular results of scalar data. * The created result is almost the same as a regular SQL result set, except * that column names are mapped to field names and data type conversions take place. */ class ScalarHydrator extends AbstractHydrator { /** * {@inheritDoc} */ protected function hydrateAllData() { $result = []; while ($data = $this->statement()->fetchAssociative()) { $this->hydrateRowData($data, $result); } return $result; } /** * {@inheritDoc} */ protected function hydrateRowData(array $row, array &$result) { $result[] = $this->gatherScalarRowData($row); } } orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php 0000644 00000013635 15120025733 0020644 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Query; use Exception; use RuntimeException; use ValueError; use function array_keys; use function array_search; use function count; use function in_array; use function key; use function reset; use function sprintf; class SimpleObjectHydrator extends AbstractHydrator { use SQLResultCasing; /** @var ClassMetadata */ private $class; /** * {@inheritDoc} */ protected function prepare() { if (count($this->resultSetMapping()->aliasMap) !== 1) { throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.'); } if ($this->resultSetMapping()->scalarMappings) { throw new RuntimeException('Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.'); } $this->class = $this->getClassMetadata(reset($this->resultSetMapping()->aliasMap)); } /** * {@inheritDoc} */ protected function cleanup() { parent::cleanup(); $this->_uow->triggerEagerLoads(); $this->_uow->hydrationComplete(); } /** * {@inheritDoc} */ protected function hydrateAllData() { $result = []; while ($row = $this->statement()->fetchAssociative()) { $this->hydrateRowData($row, $result); } $this->_em->getUnitOfWork()->triggerEagerLoads(); return $result; } /** * {@inheritDoc} */ protected function hydrateRowData(array $row, array &$result) { $entityName = $this->class->name; $data = []; $discrColumnValue = null; // We need to find the correct entity class name if we have inheritance in resultset if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { $discrColumn = $this->class->getDiscriminatorColumn(); $discrColumnName = $this->getSQLResultCasing($this->_platform, $discrColumn['name']); // Find mapped discriminator column from the result set. $metaMappingDiscrColumnName = array_search($discrColumnName, $this->resultSetMapping()->metaMappings, true); if ($metaMappingDiscrColumnName) { $discrColumnName = $metaMappingDiscrColumnName; } if (! isset($row[$discrColumnName])) { throw HydrationException::missingDiscriminatorColumn( $entityName, $discrColumnName, key($this->resultSetMapping()->aliasMap) ); } if ($row[$discrColumnName] === '') { throw HydrationException::emptyDiscriminatorValue(key( $this->resultSetMapping()->aliasMap )); } $discrMap = $this->class->discriminatorMap; if (! isset($discrMap[$row[$discrColumnName]])) { throw HydrationException::invalidDiscriminatorValue($row[$discrColumnName], array_keys($discrMap)); } $entityName = $discrMap[$row[$discrColumnName]]; $discrColumnValue = $row[$discrColumnName]; unset($row[$discrColumnName]); } foreach ($row as $column => $value) { // An ObjectHydrator should be used instead of SimpleObjectHydrator if (isset($this->resultSetMapping()->relationMap[$column])) { throw new Exception(sprintf('Unable to retrieve association information for column "%s"', $column)); } $cacheKeyInfo = $this->hydrateColumnInfo($column); if (! $cacheKeyInfo) { continue; } // If we have inheritance in resultset, make sure the field belongs to the correct class if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) { continue; } // Check if value is null before conversion (because some types convert null to something else) $valueIsNull = $value === null; // Convert field to a valid PHP value if (isset($cacheKeyInfo['type'])) { $type = $cacheKeyInfo['type']; $value = $type->convertToPHPValue($value, $this->_platform); } if ($value !== null && isset($cacheKeyInfo['enumType'])) { $originalValue = $value; try { $value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']); } catch (ValueError $e) { throw MappingException::invalidEnumValue( $entityName, $cacheKeyInfo['fieldName'], (string) $originalValue, $cacheKeyInfo['enumType'], $e ); } } $fieldName = $cacheKeyInfo['fieldName']; // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) if (! isset($data[$fieldName]) || ! $valueIsNull) { $data[$fieldName] = $value; } } if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) { $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } $uow = $this->_em->getUnitOfWork(); $entity = $uow->createEntity($entityName, $data, $this->_hints); $result[] = $entity; if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) { $this->_uow->hydrationComplete(); } } } orm/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php 0000644 00000002201 15120025733 0020616 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal\Hydration; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use function array_shift; use function count; use function key; /** * Hydrator that hydrates a single scalar value from the result set. */ class SingleScalarHydrator extends AbstractHydrator { /** * {@inheritDoc} */ protected function hydrateAllData() { $data = $this->statement()->fetchAllAssociative(); $numRows = count($data); if ($numRows === 0) { throw new NoResultException(); } if ($numRows > 1) { throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().'); } $result = $this->gatherScalarRowData($data[key($data)]); if (count($result) > 1) { throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().'); } return array_shift($result); } } orm/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php 0000644 00000011251 15120025734 0017035 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal; use Doctrine\ORM\Internal\CommitOrder\Edge; use Doctrine\ORM\Internal\CommitOrder\Vertex; use Doctrine\ORM\Internal\CommitOrder\VertexState; use Doctrine\ORM\Mapping\ClassMetadata; use function array_reverse; /** * CommitOrderCalculator implements topological sorting, which is an ordering * algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by * using a depth-first searching (DFS) to traverse the graph built in memory. * This algorithm have a linear running time based on nodes (V) and dependency * between the nodes (E), resulting in a computational complexity of O(V + E). */ class CommitOrderCalculator { /** @deprecated */ public const NOT_VISITED = VertexState::NOT_VISITED; /** @deprecated */ public const IN_PROGRESS = VertexState::IN_PROGRESS; /** @deprecated */ public const VISITED = VertexState::VISITED; /** * Matrix of nodes (aka. vertex). * * Keys are provided hashes and values are the node definition objects. * * @var array<string, Vertex> */ private $nodeList = []; /** * Volatile variable holding calculated nodes during sorting process. * * @psalm-var list<ClassMetadata> */ private $sortedNodeList = []; /** * Checks for node (vertex) existence in graph. * * @param string $hash * * @return bool */ public function hasNode($hash) { return isset($this->nodeList[$hash]); } /** * Adds a new node (vertex) to the graph, assigning its hash and value. * * @param string $hash * @param ClassMetadata $node * * @return void */ public function addNode($hash, $node) { $this->nodeList[$hash] = new Vertex($hash, $node); } /** * Adds a new dependency (edge) to the graph using their hashes. * * @param string $fromHash * @param string $toHash * @param int $weight * * @return void */ public function addDependency($fromHash, $toHash, $weight) { $this->nodeList[$fromHash]->dependencyList[$toHash] = new Edge($fromHash, $toHash, $weight); } /** * Return a valid order list of all current nodes. * The desired topological sorting is the reverse post order of these searches. * * {@internal Highly performance-sensitive method.} * * @psalm-return list<ClassMetadata> */ public function sort() { foreach ($this->nodeList as $vertex) { if ($vertex->state !== VertexState::NOT_VISITED) { continue; } $this->visit($vertex); } $sortedList = $this->sortedNodeList; $this->nodeList = []; $this->sortedNodeList = []; return array_reverse($sortedList); } /** * Visit a given node definition for reordering. * * {@internal Highly performance-sensitive method.} */ private function visit(Vertex $vertex): void { $vertex->state = VertexState::IN_PROGRESS; foreach ($vertex->dependencyList as $edge) { $adjacentVertex = $this->nodeList[$edge->to]; switch ($adjacentVertex->state) { case VertexState::VISITED: // Do nothing, since node was already visited break; case VertexState::IN_PROGRESS: if ( isset($adjacentVertex->dependencyList[$vertex->hash]) && $adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight ) { // If we have some non-visited dependencies in the in-progress dependency, we // need to visit them before adding the node. foreach ($adjacentVertex->dependencyList as $adjacentEdge) { $adjacentEdgeVertex = $this->nodeList[$adjacentEdge->to]; if ($adjacentEdgeVertex->state === VertexState::NOT_VISITED) { $this->visit($adjacentEdgeVertex); } } $adjacentVertex->state = VertexState::VISITED; $this->sortedNodeList[] = $adjacentVertex->value; } break; case VertexState::NOT_VISITED: $this->visit($adjacentVertex); } } if ($vertex->state !== VertexState::VISITED) { $vertex->state = VertexState::VISITED; $this->sortedNodeList[] = $vertex->value; } } } orm/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php 0000644 00000004051 15120025734 0017527 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Event\PostLoadEventArgs; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; /** * Class, which can handle completion of hydration cycle and produce some of tasks. * In current implementation triggers deferred postLoad event. */ final class HydrationCompleteHandler { /** @var ListenersInvoker */ private $listenersInvoker; /** @var EntityManagerInterface */ private $em; /** @var mixed[][] */ private $deferredPostLoadInvocations = []; /** * Constructor for this object */ public function __construct(ListenersInvoker $listenersInvoker, EntityManagerInterface $em) { $this->listenersInvoker = $listenersInvoker; $this->em = $em; } /** * Method schedules invoking of postLoad entity to the very end of current hydration cycle. * * @param object $entity */ public function deferPostLoadInvoking(ClassMetadata $class, $entity): void { $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad); if ($invoke === ListenersInvoker::INVOKE_NONE) { return; } $this->deferredPostLoadInvocations[] = [$class, $invoke, $entity]; } /** * This method should be called after any hydration cycle completed. * * Method fires all deferred invocations of postLoad events */ public function hydrationComplete(): void { $toInvoke = $this->deferredPostLoadInvocations; $this->deferredPostLoadInvocations = []; foreach ($toInvoke as $classAndEntity) { [$class, $invoke, $entity] = $classAndEntity; $this->listenersInvoker->invoke( $class, Events::postLoad, $entity, new PostLoadEventArgs($entity, $this->em), $invoke ); } } } orm/lib/Doctrine/ORM/Internal/SQLResultCasing.php 0000644 00000001751 15120025734 0015566 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Internal; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use function get_class; use function method_exists; use function strpos; use function strtolower; use function strtoupper; /** @internal */ trait SQLResultCasing { private function getSQLResultCasing(AbstractPlatform $platform, string $column): string { if ($platform instanceof DB2Platform || $platform instanceof OraclePlatform) { return strtoupper($column); } if ($platform instanceof PostgreSQLPlatform) { return strtolower($column); } if (strpos(get_class($platform), 'Doctrine\\DBAL\\Platforms\\') !== 0 && method_exists(AbstractPlatform::class, 'getSQLResultCasing')) { return $platform->getSQLResultCasing($column); } return $column; } } orm/lib/Doctrine/ORM/Mapping/InverseJoinColumn.php 0000644 00000000363 15120025734 0016031 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] final class InverseJoinColumn implements MappingAttribute { use JoinColumnProperties; } orm/lib/Doctrine/ORM/Mapping/SequenceGenerator.php 0000644 00000001544 15120025734 0016041 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class SequenceGenerator implements MappingAttribute { /** * @var string|null * @readonly */ public $sequenceName; /** * @var int * @readonly */ public $allocationSize = 1; /** * @var int * @readonly */ public $initialValue = 1; public function __construct( ?string $sequenceName = null, int $allocationSize = 1, int $initialValue = 1 ) { $this->sequenceName = $sequenceName; $this->allocationSize = $allocationSize; $this->initialValue = $initialValue; } } orm/lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php 0000644 00000010271 15120025734 0017570 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Mapping\ClassMetadata; use InvalidArgumentException; class AssociationBuilder { /** @var ClassMetadataBuilder */ protected $builder; /** @var mixed[] */ protected $mapping; /** @var mixed[]|null */ protected $joinColumns; /** @var int */ protected $type; /** * @param mixed[] $mapping * @param int $type */ public function __construct(ClassMetadataBuilder $builder, array $mapping, $type) { $this->builder = $builder; $this->mapping = $mapping; $this->type = $type; } /** * @param string $fieldName * * @return $this */ public function mappedBy($fieldName) { $this->mapping['mappedBy'] = $fieldName; return $this; } /** * @param string $fieldName * * @return $this */ public function inversedBy($fieldName) { $this->mapping['inversedBy'] = $fieldName; return $this; } /** @return $this */ public function cascadeAll() { $this->mapping['cascade'] = ['ALL']; return $this; } /** @return $this */ public function cascadePersist() { $this->mapping['cascade'][] = 'persist'; return $this; } /** @return $this */ public function cascadeRemove() { $this->mapping['cascade'][] = 'remove'; return $this; } /** @return $this */ public function cascadeMerge() { $this->mapping['cascade'][] = 'merge'; return $this; } /** @return $this */ public function cascadeDetach() { $this->mapping['cascade'][] = 'detach'; return $this; } /** @return $this */ public function cascadeRefresh() { $this->mapping['cascade'][] = 'refresh'; return $this; } /** @return $this */ public function fetchExtraLazy() { $this->mapping['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; return $this; } /** @return $this */ public function fetchEager() { $this->mapping['fetch'] = ClassMetadata::FETCH_EAGER; return $this; } /** @return $this */ public function fetchLazy() { $this->mapping['fetch'] = ClassMetadata::FETCH_LAZY; return $this; } /** * Add Join Columns. * * @param string $columnName * @param string $referencedColumnName * @param bool $nullable * @param bool $unique * @param string|null $onDelete * @param string|null $columnDef * * @return $this */ public function addJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null) { $this->joinColumns[] = [ 'name' => $columnName, 'referencedColumnName' => $referencedColumnName, 'nullable' => $nullable, 'unique' => $unique, 'onDelete' => $onDelete, 'columnDefinition' => $columnDef, ]; return $this; } /** * Sets field as primary key. * * @return $this */ public function makePrimaryKey() { $this->mapping['id'] = true; return $this; } /** * Removes orphan entities when detached from their parent. * * @return $this */ public function orphanRemoval() { $this->mapping['orphanRemoval'] = true; return $this; } /** * @return ClassMetadataBuilder * * @throws InvalidArgumentException */ public function build() { $mapping = $this->mapping; if ($this->joinColumns) { $mapping['joinColumns'] = $this->joinColumns; } $cm = $this->builder->getClassMetadata(); if ($this->type === ClassMetadata::MANY_TO_ONE) { $cm->mapManyToOne($mapping); } elseif ($this->type === ClassMetadata::ONE_TO_ONE) { $cm->mapOneToOne($mapping); } else { throw new InvalidArgumentException('Type should be a ToOne Association here'); } return $this->builder; } } orm/lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php 0000644 00000030521 15120025734 0020022 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; use BackedEnum; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use function get_class; /** * Builder Object for ClassMetadata * * @link www.doctrine-project.com */ class ClassMetadataBuilder { /** @var ClassMetadataInfo */ private $cm; public function __construct(ClassMetadataInfo $cm) { if (! $cm instanceof ClassMetadata) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/249', 'Passing an instance of %s to %s is deprecated, please pass a ClassMetadata instance instead.', get_class($cm), __METHOD__, ClassMetadata::class ); } $this->cm = $cm; } /** @return ClassMetadataInfo */ public function getClassMetadata() { return $this->cm; } /** * Marks the class as mapped superclass. * * @return $this */ public function setMappedSuperClass() { $this->cm->isMappedSuperclass = true; $this->cm->isEmbeddedClass = false; return $this; } /** * Marks the class as embeddable. * * @return $this */ public function setEmbeddable() { $this->cm->isEmbeddedClass = true; $this->cm->isMappedSuperclass = false; return $this; } /** * Adds and embedded class * * @param string $fieldName * @param string $class * @param string|false|null $columnPrefix * * @return $this */ public function addEmbedded($fieldName, $class, $columnPrefix = null) { $this->cm->mapEmbedded( [ 'fieldName' => $fieldName, 'class' => $class, 'columnPrefix' => $columnPrefix, ] ); return $this; } /** * Sets custom Repository class name. * * @param string $repositoryClassName * * @return $this */ public function setCustomRepositoryClass($repositoryClassName) { $this->cm->setCustomRepositoryClass($repositoryClassName); return $this; } /** * Marks class read only. * * @return $this */ public function setReadOnly() { $this->cm->markReadOnly(); return $this; } /** * Sets the table name. * * @param string $name * * @return $this */ public function setTable($name) { $this->cm->setPrimaryTable(['name' => $name]); return $this; } /** * Adds Index. * * @param string $name * @psalm-param list<string> $columns * * @return $this */ public function addIndex(array $columns, $name) { if (! isset($this->cm->table['indexes'])) { $this->cm->table['indexes'] = []; } $this->cm->table['indexes'][$name] = ['columns' => $columns]; return $this; } /** * Adds Unique Constraint. * * @param string $name * @psalm-param list<string> $columns * * @return $this */ public function addUniqueConstraint(array $columns, $name) { if (! isset($this->cm->table['uniqueConstraints'])) { $this->cm->table['uniqueConstraints'] = []; } $this->cm->table['uniqueConstraints'][$name] = ['columns' => $columns]; return $this; } /** * Adds named query. * * @param string $name * @param string $dqlQuery * * @return $this */ public function addNamedQuery($name, $dqlQuery) { $this->cm->addNamedQuery( [ 'name' => $name, 'query' => $dqlQuery, ] ); return $this; } /** * Sets class as root of a joined table inheritance hierarchy. * * @return $this */ public function setJoinedTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_JOINED); return $this; } /** * Sets class as root of a single table inheritance hierarchy. * * @return $this */ public function setSingleTableInheritance() { $this->cm->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE); return $this; } /** * Sets the discriminator column details. * * @param string $name * @param string $type * @param int $length * @psalm-param class-string<BackedEnum>|null $enumType * @psalm-param array<string, mixed> $options * * @return $this */ public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null, array $options = []) { $this->cm->setDiscriminatorColumn( [ 'name' => $name, 'type' => $type, 'length' => $length, 'columnDefinition' => $columnDefinition, 'enumType' => $enumType, 'options' => $options, ] ); return $this; } /** * Adds a subclass to this inheritance hierarchy. * * @param string $name * @param string $class * * @return $this */ public function addDiscriminatorMapClass($name, $class) { $this->cm->addDiscriminatorMapClass($name, $class); return $this; } /** * Sets deferred explicit change tracking policy. * * @return $this */ public function setChangeTrackingPolicyDeferredExplicit() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT); return $this; } /** * Sets notify change tracking policy. * * @return $this */ public function setChangeTrackingPolicyNotify() { $this->cm->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY); return $this; } /** * Adds lifecycle event. * * @param string $methodName * @param string $event * * @return $this */ public function addLifecycleEvent($methodName, $event) { $this->cm->addLifecycleCallback($methodName, $event); return $this; } /** * Adds Field. * * @param string $name * @param string $type * @psalm-param array<string, mixed> $mapping * * @return $this */ public function addField($name, $type, array $mapping = []) { $mapping['fieldName'] = $name; $mapping['type'] = $type; $this->cm->mapField($mapping); return $this; } /** * Creates a field builder. * * @param string $name * @param string $type * * @return FieldBuilder */ public function createField($name, $type) { return new FieldBuilder( $this, [ 'fieldName' => $name, 'type' => $type, ] ); } /** * Creates an embedded builder. * * @param string $fieldName * @param string $class * * @return EmbeddedBuilder */ public function createEmbedded($fieldName, $class) { return new EmbeddedBuilder( $this, [ 'fieldName' => $fieldName, 'class' => $class, 'columnPrefix' => null, ] ); } /** * Adds a simple many to one association, optionally with the inversed by field. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * * @return ClassMetadataBuilder */ public function addManyToOne($name, $targetEntity, $inversedBy = null) { $builder = $this->createManyToOne($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Creates a ManyToOne Association Builder. * * Note: This method does not add the association, you have to call build() on the AssociationBuilder. * * @param string $name * @param string $targetEntity * * @return AssociationBuilder */ public function createManyToOne($name, $targetEntity) { return new AssociationBuilder( $this, [ 'fieldName' => $name, 'targetEntity' => $targetEntity, ], ClassMetadata::MANY_TO_ONE ); } /** * Creates a OneToOne Association Builder. * * @param string $name * @param string $targetEntity * * @return AssociationBuilder */ public function createOneToOne($name, $targetEntity) { return new AssociationBuilder( $this, [ 'fieldName' => $name, 'targetEntity' => $targetEntity, ], ClassMetadata::ONE_TO_ONE ); } /** * Adds simple inverse one-to-one association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addInverseOneToOne($name, $targetEntity, $mappedBy) { $builder = $this->createOneToOne($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Adds simple owning one-to-one association. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * * @return ClassMetadataBuilder */ public function addOwningOneToOne($name, $targetEntity, $inversedBy = null) { $builder = $this->createOneToOne($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Creates a ManyToMany Association Builder. * * @param string $name * @param string $targetEntity * * @return ManyToManyAssociationBuilder */ public function createManyToMany($name, $targetEntity) { return new ManyToManyAssociationBuilder( $this, [ 'fieldName' => $name, 'targetEntity' => $targetEntity, ], ClassMetadata::MANY_TO_MANY ); } /** * Adds a simple owning many to many association. * * @param string $name * @param string $targetEntity * @param string|null $inversedBy * * @return ClassMetadataBuilder */ public function addOwningManyToMany($name, $targetEntity, $inversedBy = null) { $builder = $this->createManyToMany($name, $targetEntity); if ($inversedBy) { $builder->inversedBy($inversedBy); } return $builder->build(); } /** * Adds a simple inverse many to many association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addInverseManyToMany($name, $targetEntity, $mappedBy) { $builder = $this->createManyToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } /** * Creates a one to many association builder. * * @param string $name * @param string $targetEntity * * @return OneToManyAssociationBuilder */ public function createOneToMany($name, $targetEntity) { return new OneToManyAssociationBuilder( $this, [ 'fieldName' => $name, 'targetEntity' => $targetEntity, ], ClassMetadata::ONE_TO_MANY ); } /** * Adds simple OneToMany association. * * @param string $name * @param string $targetEntity * @param string $mappedBy * * @return ClassMetadataBuilder */ public function addOneToMany($name, $targetEntity, $mappedBy) { $builder = $this->createOneToMany($name, $targetEntity); $builder->mappedBy($mappedBy); return $builder->build(); } } orm/lib/Doctrine/ORM/Mapping/Builder/EmbeddedBuilder.php 0000644 00000002174 15120025734 0017010 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; /** * Embedded Builder * * @link www.doctrine-project.com */ class EmbeddedBuilder { /** @var ClassMetadataBuilder */ private $builder; /** @var mixed[] */ private $mapping; /** @param mixed[] $mapping */ public function __construct(ClassMetadataBuilder $builder, array $mapping) { $this->builder = $builder; $this->mapping = $mapping; } /** * Sets the column prefix for all of the embedded columns. * * @param string $columnPrefix * * @return $this */ public function setColumnPrefix($columnPrefix) { $this->mapping['columnPrefix'] = $columnPrefix; return $this; } /** * Finalizes this embeddable and attach it to the ClassMetadata. * * Without this call an EmbeddedBuilder has no effect on the ClassMetadata. * * @return ClassMetadataBuilder */ public function build() { $cm = $this->builder->getClassMetadata(); $cm->mapEmbedded($this->mapping); return $this->builder; } } orm/lib/Doctrine/ORM/Mapping/Builder/EntityListenerBuilder.php 0000644 00000003043 15120025734 0020275 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use function class_exists; use function get_class_methods; /** * Builder for entity listeners. */ class EntityListenerBuilder { /** @var array<string,bool> Hash-map to handle event names. */ private static $events = [ Events::preRemove => true, Events::postRemove => true, Events::prePersist => true, Events::postPersist => true, Events::preUpdate => true, Events::postUpdate => true, Events::postLoad => true, Events::preFlush => true, ]; /** * Lookup the entity class to find methods that match to event lifecycle names * * @param ClassMetadata $metadata The entity metadata. * @param string $className The listener class name. * * @return void * * @throws MappingException When the listener class not found. */ public static function bindEntityListener(ClassMetadata $metadata, $className) { $class = $metadata->fullyQualifiedClassName($className); if (! class_exists($class)) { throw MappingException::entityListenerClassNotFound($class, $className); } foreach (get_class_methods($class) as $method) { if (! isset(self::$events[$method])) { continue; } $metadata->addEntityListener($method, $class, $method); } } } orm/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php 0000644 00000012633 15120025734 0016343 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; use function constant; /** * Field Builder * * @link www.doctrine-project.com */ class FieldBuilder { /** @var ClassMetadataBuilder */ private $builder; /** @var mixed[] */ private $mapping; /** @var bool */ private $version; /** @var string */ private $generatedValue; /** @var mixed[] */ private $sequenceDef; /** @var string|null */ private $customIdGenerator; /** @param mixed[] $mapping */ public function __construct(ClassMetadataBuilder $builder, array $mapping) { $this->builder = $builder; $this->mapping = $mapping; } /** * Sets length. * * @param int $length * * @return $this */ public function length($length) { $this->mapping['length'] = $length; return $this; } /** * Sets nullable. * * @param bool $flag * * @return $this */ public function nullable($flag = true) { $this->mapping['nullable'] = (bool) $flag; return $this; } /** * Sets Unique. * * @param bool $flag * * @return $this */ public function unique($flag = true) { $this->mapping['unique'] = (bool) $flag; return $this; } /** * Sets column name. * * @param string $name * * @return $this */ public function columnName($name) { $this->mapping['columnName'] = $name; return $this; } /** * Sets Precision. * * @param int $p * * @return $this */ public function precision($p) { $this->mapping['precision'] = $p; return $this; } /** * Sets insertable. * * @return $this */ public function insertable(bool $flag = true): self { if (! $flag) { $this->mapping['notInsertable'] = true; } return $this; } /** * Sets updatable. * * @return $this */ public function updatable(bool $flag = true): self { if (! $flag) { $this->mapping['notUpdatable'] = true; } return $this; } /** * Sets scale. * * @param int $s * * @return $this */ public function scale($s) { $this->mapping['scale'] = $s; return $this; } /** * Sets field as primary key. * * @deprecated Use makePrimaryKey() instead * * @return FieldBuilder */ public function isPrimaryKey() { return $this->makePrimaryKey(); } /** * Sets field as primary key. * * @return $this */ public function makePrimaryKey() { $this->mapping['id'] = true; return $this; } /** * Sets an option. * * @param string $name * @param mixed $value * * @return $this */ public function option($name, $value) { $this->mapping['options'][$name] = $value; return $this; } /** * @param string $strategy * * @return $this */ public function generatedValue($strategy = 'AUTO') { $this->generatedValue = $strategy; return $this; } /** * Sets field versioned. * * @return $this */ public function isVersionField() { $this->version = true; return $this; } /** * Sets Sequence Generator. * * @param string $sequenceName * @param int $allocationSize * @param int $initialValue * * @return $this */ public function setSequenceGenerator($sequenceName, $allocationSize = 1, $initialValue = 1) { $this->sequenceDef = [ 'sequenceName' => $sequenceName, 'allocationSize' => $allocationSize, 'initialValue' => $initialValue, ]; return $this; } /** * Sets column definition. * * @param string $def * * @return $this */ public function columnDefinition($def) { $this->mapping['columnDefinition'] = $def; return $this; } /** * Set the FQCN of the custom ID generator. * This class must extend \Doctrine\ORM\Id\AbstractIdGenerator. * * @param string $customIdGenerator * * @return $this */ public function setCustomIdGenerator($customIdGenerator) { $this->customIdGenerator = (string) $customIdGenerator; return $this; } /** * Finalizes this field and attach it to the ClassMetadata. * * Without this call a FieldBuilder has no effect on the ClassMetadata. * * @return ClassMetadataBuilder */ public function build() { $cm = $this->builder->getClassMetadata(); if ($this->generatedValue) { $cm->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $this->generatedValue)); } if ($this->version) { $cm->setVersionMapping($this->mapping); } $cm->mapField($this->mapping); if ($this->sequenceDef) { $cm->setSequenceGeneratorDefinition($this->sequenceDef); } if ($this->customIdGenerator) { $cm->setCustomGeneratorDefinition(['class' => $this->customIdGenerator]); } return $this->builder; } } orm/lib/Doctrine/ORM/Mapping/Builder/ManyToManyAssociationBuilder.php 0000644 00000003705 15120025734 0021551 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; /** * ManyToMany Association Builder * * @link www.doctrine-project.com */ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder { /** @var string|null */ private $joinTableName; /** @var mixed[] */ private $inverseJoinColumns = []; /** * @param string $name * * @return $this */ public function setJoinTable($name) { $this->joinTableName = $name; return $this; } /** * Adds Inverse Join Columns. * * @param string $columnName * @param string $referencedColumnName * @param bool $nullable * @param bool $unique * @param string|null $onDelete * @param string|null $columnDef * * @return $this */ public function addInverseJoinColumn($columnName, $referencedColumnName, $nullable = true, $unique = false, $onDelete = null, $columnDef = null) { $this->inverseJoinColumns[] = [ 'name' => $columnName, 'referencedColumnName' => $referencedColumnName, 'nullable' => $nullable, 'unique' => $unique, 'onDelete' => $onDelete, 'columnDefinition' => $columnDef, ]; return $this; } /** @return ClassMetadataBuilder */ public function build() { $mapping = $this->mapping; $mapping['joinTable'] = []; if ($this->joinColumns) { $mapping['joinTable']['joinColumns'] = $this->joinColumns; } if ($this->inverseJoinColumns) { $mapping['joinTable']['inverseJoinColumns'] = $this->inverseJoinColumns; } if ($this->joinTableName) { $mapping['joinTable']['name'] = $this->joinTableName; } $cm = $this->builder->getClassMetadata(); $cm->mapManyToMany($mapping); return $this->builder; } } orm/lib/Doctrine/ORM/Mapping/Builder/OneToManyAssociationBuilder.php 0000644 00000001745 15120025734 0021370 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Builder; /** * OneToMany Association Builder * * @link www.doctrine-project.com */ class OneToManyAssociationBuilder extends AssociationBuilder { /** * @psalm-param array<string, string> $fieldNames * * @return $this */ public function setOrderBy(array $fieldNames) { $this->mapping['orderBy'] = $fieldNames; return $this; } /** * @param string $fieldName * * @return $this */ public function setIndexBy($fieldName) { $this->mapping['indexBy'] = $fieldName; return $this; } /** @return ClassMetadataBuilder */ public function build() { $mapping = $this->mapping; if ($this->joinColumns) { $mapping['joinColumns'] = $this->joinColumns; } $cm = $this->builder->getClassMetadata(); $cm->mapOneToMany($mapping); return $this->builder; } } orm/lib/Doctrine/ORM/Mapping/DefaultQuoteStrategy.php 0000644 00000011616 15120025734 0016550 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Internal\SQLResultCasing; use function array_map; use function array_merge; use function is_numeric; use function preg_replace; use function substr; /** * A set of rules for determining the physical column, alias and table quotes */ class DefaultQuoteStrategy implements QuoteStrategy { use SQLResultCasing; /** * {@inheritDoc} */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform) { return isset($class->fieldMappings[$fieldName]['quoted']) ? $platform->quoteIdentifier($class->fieldMappings[$fieldName]['columnName']) : $class->fieldMappings[$fieldName]['columnName']; } /** * {@inheritDoc} * * @todo Table names should be computed in DBAL depending on the platform */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform) { $tableName = $class->table['name']; if (! empty($class->table['schema'])) { $tableName = $class->table['schema'] . '.' . $class->table['name']; if (! $platform->supportsSchemas() && $platform->canEmulateSchemas()) { $tableName = $class->table['schema'] . '__' . $class->table['name']; } } return isset($class->table['quoted']) ? $platform->quoteIdentifier($tableName) : $tableName; } /** * {@inheritDoc} */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform) { return isset($definition['quoted']) ? $platform->quoteIdentifier($definition['sequenceName']) : $definition['sequenceName']; } /** * {@inheritDoc} */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; } /** * {@inheritDoc} */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['referencedColumnName']) : $joinColumn['referencedColumnName']; } /** * {@inheritDoc} */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform) { $schema = ''; if (isset($association['joinTable']['schema'])) { $schema = $association['joinTable']['schema']; $schema .= ! $platform->supportsSchemas() && $platform->canEmulateSchemas() ? '__' : '.'; } $tableName = $association['joinTable']['name']; if (isset($association['joinTable']['quoted'])) { $tableName = $platform->quoteIdentifier($tableName); } return $schema . $tableName; } /** * {@inheritDoc} */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform) { $quotedColumnNames = []; foreach ($class->identifier as $fieldName) { if (isset($class->fieldMappings[$fieldName])) { $quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform); continue; } // Association defined as Id field $joinColumns = $class->associationMappings[$fieldName]['joinColumns']; $assocQuotedColumnNames = array_map( static function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * {@inheritDoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null) { // 1 ) Concatenate column name and counter // 2 ) Trim the column alias to the maximum identifier length of the platform. // If the alias is to long, characters are cut off from the beginning. // 3 ) Strip non alphanumeric characters // 4 ) Prefix with "_" if the result its numeric $columnName .= '_' . $counter; $columnName = substr($columnName, -$platform->getMaxIdentifierLength()); $columnName = preg_replace('/[^A-Za-z0-9_]/', '', $columnName); $columnName = is_numeric($columnName) ? '_' . $columnName : $columnName; return $this->getSQLResultCasing($platform, $columnName); } } orm/lib/Doctrine/ORM/Mapping/DefaultTypedFieldMapper.php 0000644 00000004144 15120025734 0017124 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use DateInterval; use DateTime; use DateTimeImmutable; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use ReflectionEnum; use ReflectionNamedType; use ReflectionProperty; use function array_merge; use function assert; use function enum_exists; use const PHP_VERSION_ID; /** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */ final class DefaultTypedFieldMapper implements TypedFieldMapper { /** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */ private $typedFieldMappings; private const DEFAULT_TYPED_FIELD_MAPPINGS = [ DateInterval::class => Types::DATEINTERVAL, DateTime::class => Types::DATETIME_MUTABLE, DateTimeImmutable::class => Types::DATETIME_IMMUTABLE, 'array' => Types::JSON, 'bool' => Types::BOOLEAN, 'float' => Types::FLOAT, 'int' => Types::INTEGER, 'string' => Types::STRING, ]; /** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */ public function __construct(array $typedFieldMappings = []) { $this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings); } /** * {@inheritDoc} */ public function validateAndComplete(array $mapping, ReflectionProperty $field): array { $type = $field->getType(); if ( ! isset($mapping['type']) && ($type instanceof ReflectionNamedType) ) { if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) { $mapping['enumType'] = $type->getName(); $reflection = new ReflectionEnum($type->getName()); $type = $reflection->getBackingType(); assert($type instanceof ReflectionNamedType); } if (isset($this->typedFieldMappings[$type->getName()])) { $mapping['type'] = $this->typedFieldMappings[$type->getName()]; } } return $mapping; } } orm/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php 0000644 00000002701 15120025734 0016403 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class DiscriminatorColumn implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var string|null * @readonly */ public $type; /** * @var int|null * @readonly */ public $length; /** * @var string|null * @readonly */ public $columnDefinition; /** * @var class-string<\BackedEnum>|null * @readonly */ public $enumType = null; /** * @var array<string, mixed> * @readonly */ public $options = []; /** * @param class-string<\BackedEnum>|null $enumType * @param array<string, mixed> $options */ public function __construct( ?string $name = null, ?string $type = null, ?int $length = null, ?string $columnDefinition = null, ?string $enumType = null, array $options = [] ) { $this->name = $name; $this->type = $type; $this->length = $length; $this->columnDefinition = $columnDefinition; $this->enumType = $enumType; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php 0000644 00000001053 15120025734 0015662 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class DiscriminatorMap implements MappingAttribute { /** * @var array<int|string, string> * @readonly */ public $value; /** @param array<int|string, string> $value */ public function __construct(array $value) { $this->value = $value; } } orm/lib/Doctrine/ORM/Mapping/Embedded.php 0000644 00000001224 15120025734 0014106 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class Embedded implements MappingAttribute { /** * @var string|null * @readonly */ public $class; /** * @var string|bool|null * @readonly */ public $columnPrefix; public function __construct(?string $class = null, $columnPrefix = null) { $this->class = $class; $this->columnPrefix = $columnPrefix; } } orm/lib/Doctrine/ORM/Mapping/EntityListenerResolver.php 0000644 00000001573 15120025734 0017130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * A resolver is used to instantiate an entity listener. */ interface EntityListenerResolver { /** * Clear all instances from the set, or a specific instance when given its identifier. * * @param string|null $className May be any arbitrary string. Name kept for BC only. * * @return void */ public function clear($className = null); /** * Returns a entity listener instance for the given identifier. * * @param string $className May be any arbitrary string. Name kept for BC only. * * @return object An entity listener */ public function resolve($className); /** * Register a entity listener instance. * * @param object $object An entity listener * * @return void */ public function register($object); } orm/lib/Doctrine/ORM/Mapping/EntityResult.php 0000644 00000001763 15120025734 0015100 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * References an entity in the SELECT clause of a SQL query. * If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object. * This should include foreign key columns to related entities. * The results obtained when insufficient data is available are undefined. * * @Annotation * @Target("ANNOTATION") */ final class EntityResult implements MappingAttribute { /** * The class of the result. * * @var string */ public $entityClass; /** * Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @var array<\Doctrine\ORM\Mapping\FieldResult> */ public $fields = []; /** * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. * * @var string */ public $discriminatorColumn; } orm/lib/Doctrine/ORM/Mapping/GeneratedValue.php 0000644 00000001420 15120025734 0015306 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class GeneratedValue implements MappingAttribute { /** * The type of ID generator. * * @var string * @psalm-var 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM' * @readonly * @Enum({"AUTO", "SEQUENCE", "TABLE", "IDENTITY", "NONE", "UUID", "CUSTOM"}) */ public $strategy = 'AUTO'; /** @psalm-param 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM' $strategy */ public function __construct(string $strategy = 'AUTO') { $this->strategy = $strategy; } } orm/lib/Doctrine/ORM/Mapping/Id.php 0000644 00000000330 15120025734 0012746 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class Id implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/InheritanceType.php 0000644 00000002202 15120025734 0015505 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\Deprecations\Deprecation; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class InheritanceType implements MappingAttribute { /** * The inheritance type used by the class and its subclasses. * * @var string * @psalm-var 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS' * @readonly * @Enum({"NONE", "JOINED", "SINGLE_TABLE", "TABLE_PER_CLASS"}) */ public $value; /** @psalm-param 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS' $value */ public function __construct(string $value) { if ($value === 'TABLE_PER_CLASS') { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10414/', 'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement' ); } $this->value = $value; } } orm/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php 0000644 00000002500 15120025734 0017750 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use InvalidArgumentException; use LogicException; use ReflectionProperty; use function assert; use function func_get_args; use function func_num_args; use function is_object; use function sprintf; /** @internal */ final class ReflectionReadonlyProperty extends ReflectionProperty { public function __construct( private ReflectionProperty $wrappedProperty ) { if (! $wrappedProperty->isReadOnly()) { throw new InvalidArgumentException('Given property is not readonly.'); } parent::__construct($wrappedProperty->class, $wrappedProperty->name); } public function getValue(?object $object = null): mixed { return $this->wrappedProperty->getValue(...func_get_args()); } public function setValue(mixed $objectOrValue, mixed $value = null): void { if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) { $this->wrappedProperty->setValue(...func_get_args()); return; } assert(is_object($objectOrValue)); if (parent::getValue($objectOrValue) !== $value) { throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name)); } } } orm/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php 0000644 00000103403 15120025734 0017140 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\Reader; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use UnexpectedValueException; use function assert; use function class_exists; use function constant; use function count; use function defined; use function get_class; use function is_array; use function is_numeric; /** * The AnnotationDriver reads the mapping metadata from docblock annotations. */ class AnnotationDriver extends CompatibilityAnnotationDriver { use ColocatedMappingDriver; /** * The annotation reader. * * @internal this property will be private in 3.0 * * @var Reader */ protected $reader; /** * @var int[] * @psalm-var array<class-string, int> */ protected $entityAnnotationClasses = [ Mapping\Entity::class => 1, Mapping\MappedSuperclass::class => 2, ]; /** * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. * * @param Reader $reader The AnnotationReader to use * @param string|string[]|null $paths One or multiple paths where mapping classes can be found. */ public function __construct($reader, $paths = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/10098', 'The annotation mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to the attribute or XML driver.' ); $this->reader = $reader; $this->addPaths((array) $paths); } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @template T of object */ public function loadMetadataForClass($className, PersistenceClassMetadata $metadata) { $class = $metadata->getReflectionClass() // this happens when running annotation driver in combination with // static reflection services. This is not the nicest fix ?? new ReflectionClass($metadata->name); $classAnnotations = $this->reader->getClassAnnotations($class); foreach ($classAnnotations as $key => $annot) { if (! is_numeric($key)) { continue; } $classAnnotations[get_class($annot)] = $annot; } // Evaluate Entity annotation if (isset($classAnnotations[Mapping\Entity::class])) { $entityAnnot = $classAnnotations[Mapping\Entity::class]; assert($entityAnnot instanceof Mapping\Entity); if ($entityAnnot->repositoryClass !== null) { $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass); } if ($entityAnnot->readOnly) { $metadata->markReadOnly(); } } elseif (isset($classAnnotations[Mapping\MappedSuperclass::class])) { $mappedSuperclassAnnot = $classAnnotations[Mapping\MappedSuperclass::class]; assert($mappedSuperclassAnnot instanceof Mapping\MappedSuperclass); $metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass); $metadata->isMappedSuperclass = true; } elseif (isset($classAnnotations[Mapping\Embeddable::class])) { $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate Table annotation if (isset($classAnnotations[Mapping\Table::class])) { $tableAnnot = $classAnnotations[Mapping\Table::class]; assert($tableAnnot instanceof Mapping\Table); $primaryTable = [ 'name' => $tableAnnot->name, 'schema' => $tableAnnot->schema, ]; foreach ($tableAnnot->indexes ?? [] as $indexAnnot) { $index = []; if (! empty($indexAnnot->columns)) { $index['columns'] = $indexAnnot->columns; } if (! empty($indexAnnot->fields)) { $index['fields'] = $indexAnnot->fields; } if ( isset($index['columns'], $index['fields']) || ( ! isset($index['columns']) && ! isset($index['fields']) ) ) { throw MappingException::invalidIndexConfiguration( $className, (string) ($indexAnnot->name ?? count($primaryTable['indexes'])) ); } if (! empty($indexAnnot->flags)) { $index['flags'] = $indexAnnot->flags; } if (! empty($indexAnnot->options)) { $index['options'] = $indexAnnot->options; } if (! empty($indexAnnot->name)) { $primaryTable['indexes'][$indexAnnot->name] = $index; } else { $primaryTable['indexes'][] = $index; } } foreach ($tableAnnot->uniqueConstraints ?? [] as $uniqueConstraintAnnot) { $uniqueConstraint = []; if (! empty($uniqueConstraintAnnot->columns)) { $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns; } if (! empty($uniqueConstraintAnnot->fields)) { $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields; } if ( isset($uniqueConstraint['columns'], $uniqueConstraint['fields']) || ( ! isset($uniqueConstraint['columns']) && ! isset($uniqueConstraint['fields']) ) ) { throw MappingException::invalidUniqueConstraintConfiguration( $className, (string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints'])) ); } if (! empty($uniqueConstraintAnnot->options)) { $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; } if (! empty($uniqueConstraintAnnot->name)) { $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; } else { $primaryTable['uniqueConstraints'][] = $uniqueConstraint; } } if ($tableAnnot->options) { $primaryTable['options'] = $tableAnnot->options; } $metadata->setPrimaryTable($primaryTable); } // Evaluate @Cache annotation if (isset($classAnnotations[Mapping\Cache::class])) { $cacheAnnot = $classAnnotations[Mapping\Cache::class]; $cacheMap = [ 'region' => $cacheAnnot->region, 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), ]; $metadata->enableCache($cacheMap); } // Evaluate NamedNativeQueries annotation if (isset($classAnnotations[Mapping\NamedNativeQueries::class])) { $namedNativeQueriesAnnot = $classAnnotations[Mapping\NamedNativeQueries::class]; foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) { $metadata->addNamedNativeQuery( [ 'name' => $namedNativeQuery->name, 'query' => $namedNativeQuery->query, 'resultClass' => $namedNativeQuery->resultClass, 'resultSetMapping' => $namedNativeQuery->resultSetMapping, ] ); } } // Evaluate SqlResultSetMappings annotation if (isset($classAnnotations[Mapping\SqlResultSetMappings::class])) { $sqlResultSetMappingsAnnot = $classAnnotations[Mapping\SqlResultSetMappings::class]; foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { $entities = []; $columns = []; foreach ($resultSetMapping->entities as $entityResultAnnot) { $entityResult = [ 'fields' => [], 'entityClass' => $entityResultAnnot->entityClass, 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn, ]; foreach ($entityResultAnnot->fields as $fieldResultAnnot) { $entityResult['fields'][] = [ 'name' => $fieldResultAnnot->name, 'column' => $fieldResultAnnot->column, ]; } $entities[] = $entityResult; } foreach ($resultSetMapping->columns as $columnResultAnnot) { $columns[] = [ 'name' => $columnResultAnnot->name, ]; } $metadata->addSqlResultSetMapping( [ 'name' => $resultSetMapping->name, 'entities' => $entities, 'columns' => $columns, ] ); } } // Evaluate NamedQueries annotation if (isset($classAnnotations[Mapping\NamedQueries::class])) { $namedQueriesAnnot = $classAnnotations[Mapping\NamedQueries::class]; if (! is_array($namedQueriesAnnot->value)) { throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.'); } foreach ($namedQueriesAnnot->value as $namedQuery) { if (! ($namedQuery instanceof Mapping\NamedQuery)) { throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.'); } $metadata->addNamedQuery( [ 'name' => $namedQuery->name, 'query' => $namedQuery->query, ] ); } } // Evaluate InheritanceType annotation if (isset($classAnnotations[Mapping\InheritanceType::class])) { $inheritanceTypeAnnot = $classAnnotations[Mapping\InheritanceType::class]; assert($inheritanceTypeAnnot instanceof Mapping\InheritanceType); $metadata->setInheritanceType( constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value) ); if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate DiscriminatorColumn annotation if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) { $discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class]; assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn); $columnDef = [ 'name' => $discrColumnAnnot->name, 'type' => $discrColumnAnnot->type ?: 'string', 'length' => $discrColumnAnnot->length ?? 255, 'columnDefinition' => $discrColumnAnnot->columnDefinition, 'enumType' => $discrColumnAnnot->enumType, ]; if ($discrColumnAnnot->options) { $columnDef['options'] = $discrColumnAnnot->options; } $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } // Evaluate DiscriminatorMap annotation if (isset($classAnnotations[Mapping\DiscriminatorMap::class])) { $discrMapAnnot = $classAnnotations[Mapping\DiscriminatorMap::class]; assert($discrMapAnnot instanceof Mapping\DiscriminatorMap); $metadata->setDiscriminatorMap($discrMapAnnot->value); } } } // Evaluate DoctrineChangeTrackingPolicy annotation if (isset($classAnnotations[Mapping\ChangeTrackingPolicy::class])) { $changeTrackingAnnot = $classAnnotations[Mapping\ChangeTrackingPolicy::class]; assert($changeTrackingAnnot instanceof Mapping\ChangeTrackingPolicy); $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value)); } // Evaluate annotations on properties/fields foreach ($class->getProperties() as $property) { if ( $metadata->isMappedSuperclass && ! $property->isPrivate() || $metadata->isInheritedField($property->name) || $metadata->isInheritedAssociation($property->name) || $metadata->isInheritedEmbeddedClass($property->name) ) { continue; } $mapping = []; $mapping['fieldName'] = $property->getName(); // Evaluate @Cache annotation $cacheAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class); if ($cacheAnnot !== null) { $mapping['cache'] = $metadata->getAssociationCacheDefaults( $mapping['fieldName'], [ 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), 'region' => $cacheAnnot->region, ] ); } // Check for JoinColumn/JoinColumns annotations $joinColumns = []; $joinColumnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class); if ($joinColumnAnnot) { $joinColumns[] = $this->joinColumnToArray($joinColumnAnnot); } else { $joinColumnsAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumns::class); if ($joinColumnsAnnot) { foreach ($joinColumnsAnnot->value as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } } } // Field can only be annotated with one of: // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany $columnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Column::class); if ($columnAnnot) { $mapping = $this->columnToArray($property->getName(), $columnAnnot); $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class); if ($idAnnot) { $mapping['id'] = true; } $generatedValueAnnot = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class); if ($generatedValueAnnot) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy)); } if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition $seqGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class); if ($seqGeneratorAnnot) { $metadata->setSequenceGeneratorDefinition( [ 'sequenceName' => $seqGeneratorAnnot->sequenceName, 'allocationSize' => $seqGeneratorAnnot->allocationSize, 'initialValue' => $seqGeneratorAnnot->initialValue, ] ); } else { $customGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class); if ($customGeneratorAnnot) { $metadata->setCustomGeneratorDefinition( [ 'class' => $customGeneratorAnnot->class, ] ); } } } else { $this->loadRelationShipMapping( $property, $mapping, $metadata, $joinColumns, $className ); } } // Evaluate AssociationOverrides annotation if (isset($classAnnotations[Mapping\AssociationOverrides::class])) { $associationOverridesAnnot = $classAnnotations[Mapping\AssociationOverrides::class]; assert($associationOverridesAnnot instanceof Mapping\AssociationOverrides); foreach ($associationOverridesAnnot->overrides as $associationOverride) { $override = []; $fieldName = $associationOverride->name; // Check for JoinColumn/JoinColumns annotations if ($associationOverride->joinColumns) { $joinColumns = []; foreach ($associationOverride->joinColumns as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } $override['joinColumns'] = $joinColumns; } // Check for JoinTable annotations if ($associationOverride->joinTable) { $joinTableAnnot = $associationOverride->joinTable; $joinTable = [ 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema, ]; foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } $override['joinTable'] = $joinTable; } // Check for inversedBy if ($associationOverride->inversedBy) { $override['inversedBy'] = $associationOverride->inversedBy; } // Check for `fetch` if ($associationOverride->fetch) { $override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate AttributeOverrides annotation if (isset($classAnnotations[Mapping\AttributeOverrides::class])) { $attributeOverridesAnnot = $classAnnotations[Mapping\AttributeOverrides::class]; assert($attributeOverridesAnnot instanceof Mapping\AttributeOverrides); foreach ($attributeOverridesAnnot->overrides as $attributeOverrideAnnot) { $attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column); $metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride); } } // Evaluate EntityListeners annotation if (isset($classAnnotations[Mapping\EntityListeners::class])) { $entityListenersAnnot = $classAnnotations[Mapping\EntityListeners::class]; assert($entityListenersAnnot instanceof Mapping\EntityListeners); foreach ($entityListenersAnnot->value as $item) { $listenerClassName = $metadata->fullyQualifiedClassName($item); if (! class_exists($listenerClassName)) { throw MappingException::entityListenerClassNotFound($listenerClassName, $className); } $hasMapping = false; $listenerClass = new ReflectionClass($listenerClassName); foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { // find method callbacks. $callbacks = $this->getMethodCallbacks($method); $hasMapping = $hasMapping ?: ! empty($callbacks); foreach ($callbacks as $value) { $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); } } // Evaluate the listener using naming convention. if (! $hasMapping) { EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); } } } // Evaluate @HasLifecycleCallbacks annotation if (isset($classAnnotations[Mapping\HasLifecycleCallbacks::class])) { foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { foreach ($this->getMethodCallbacks($method) as $value) { $metadata->addLifecycleCallback($value[0], $value[1]); } } } } /** * @param mixed[] $joinColumns * @param class-string $className * @param array<string, mixed> $mapping */ private function loadRelationShipMapping( ReflectionProperty $property, array &$mapping, PersistenceClassMetadata $metadata, array $joinColumns, string $className ): void { $oneToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class); if ($oneToOneAnnot) { $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class); if ($idAnnot) { $mapping['id'] = true; } $mapping['targetEntity'] = $oneToOneAnnot->targetEntity; $mapping['joinColumns'] = $joinColumns; $mapping['mappedBy'] = $oneToOneAnnot->mappedBy; $mapping['inversedBy'] = $oneToOneAnnot->inversedBy; $mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch); $metadata->mapOneToOne($mapping); return; } $oneToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class); if ($oneToManyAnnot) { $mapping['mappedBy'] = $oneToManyAnnot->mappedBy; $mapping['targetEntity'] = $oneToManyAnnot->targetEntity; $mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['indexBy'] = $oneToManyAnnot->indexBy; $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch); $orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class); if ($orderByAnnot) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapOneToMany($mapping); } $manyToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class); if ($manyToOneAnnot) { $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class); if ($idAnnot) { $mapping['id'] = true; } $mapping['joinColumns'] = $joinColumns; $mapping['cascade'] = $manyToOneAnnot->cascade; $mapping['inversedBy'] = $manyToOneAnnot->inversedBy; $mapping['targetEntity'] = $manyToOneAnnot->targetEntity; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch); $metadata->mapManyToOne($mapping); } $manyToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class); if ($manyToManyAnnot) { $joinTable = []; $joinTableAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class); if ($joinTableAnnot) { $joinTable = [ 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema, ]; if ($joinTableAnnot->options) { $joinTable['options'] = $joinTableAnnot->options; } foreach ($joinTableAnnot->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } } $mapping['joinTable'] = $joinTable; $mapping['targetEntity'] = $manyToManyAnnot->targetEntity; $mapping['mappedBy'] = $manyToManyAnnot->mappedBy; $mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['indexBy'] = $manyToManyAnnot->indexBy; $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch); $orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class); if ($orderByAnnot) { $mapping['orderBy'] = $orderByAnnot->value; } $metadata->mapManyToMany($mapping); } $embeddedAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class); if ($embeddedAnnot) { $mapping['class'] = $embeddedAnnot->class; $mapping['columnPrefix'] = $embeddedAnnot->columnPrefix; $metadata->mapEmbedded($mapping); } } /** * Attempts to resolve the fetch mode. * * @param class-string $className * * @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata. * * @throws MappingException If the fetch mode is not valid. */ private function getFetchMode(string $className, string $fetchMode): int { if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { throw MappingException::invalidFetchMode($className, $fetchMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); } /** * Attempts to resolve the generated mode. * * @psalm-return ClassMetadata::GENERATED_* * * @throws MappingException If the fetch mode is not valid. */ private function getGeneratedMode(string $generatedMode): int { if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) { throw MappingException::invalidGeneratedMode($generatedMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode); } /** * Parses the given method. * * @return list<array{string, string}> * @psalm-return list<array{string, (Events::*)}> */ private function getMethodCallbacks(ReflectionMethod $method): array { $callbacks = []; $annotations = $this->reader->getMethodAnnotations($method); foreach ($annotations as $annot) { if ($annot instanceof Mapping\PrePersist) { $callbacks[] = [$method->name, Events::prePersist]; } if ($annot instanceof Mapping\PostPersist) { $callbacks[] = [$method->name, Events::postPersist]; } if ($annot instanceof Mapping\PreUpdate) { $callbacks[] = [$method->name, Events::preUpdate]; } if ($annot instanceof Mapping\PostUpdate) { $callbacks[] = [$method->name, Events::postUpdate]; } if ($annot instanceof Mapping\PreRemove) { $callbacks[] = [$method->name, Events::preRemove]; } if ($annot instanceof Mapping\PostRemove) { $callbacks[] = [$method->name, Events::postRemove]; } if ($annot instanceof Mapping\PostLoad) { $callbacks[] = [$method->name, Events::postLoad]; } if ($annot instanceof Mapping\PreFlush) { $callbacks[] = [$method->name, Events::preFlush]; } } return $callbacks; } /** * Parse the given JoinColumn as array * * @return mixed[] * @psalm-return array{ * name: string|null, * unique: bool, * nullable: bool, * onDelete: mixed, * columnDefinition: string|null, * referencedColumnName: string, * options?: array<string, mixed> * } */ private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array { $mapping = [ 'name' => $joinColumn->name, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, 'columnDefinition' => $joinColumn->columnDefinition, 'referencedColumnName' => $joinColumn->referencedColumnName, ]; if ($joinColumn->options) { $mapping['options'] = $joinColumn->options; } return $mapping; } /** * Parse the given Column as array * * @return mixed[] * @psalm-return array{ * fieldName: string, * type: mixed, * scale: int, * length: int, * unique: bool, * nullable: bool, * precision: int, * notInsertable?: bool, * notUpdateble?: bool, * generated?: ClassMetadata::GENERATED_*, * enumType?: class-string, * options?: mixed[], * columnName?: string, * columnDefinition?: string * } */ private function columnToArray(string $fieldName, Mapping\Column $column): array { $mapping = [ 'fieldName' => $fieldName, 'type' => $column->type, 'scale' => $column->scale, 'length' => $column->length, 'unique' => $column->unique, 'nullable' => $column->nullable, 'precision' => $column->precision, ]; if (! $column->insertable) { $mapping['notInsertable'] = true; } if (! $column->updatable) { $mapping['notUpdatable'] = true; } if ($column->generated) { $mapping['generated'] = $this->getGeneratedMode($column->generated); } if ($column->options) { $mapping['options'] = $column->options; } if (isset($column->name)) { $mapping['columnName'] = $column->name; } if (isset($column->columnDefinition)) { $mapping['columnDefinition'] = $column->columnDefinition; } if ($column->enumType !== null) { $mapping['enumType'] = $column->enumType; } return $mapping; } /** * Retrieve the current annotation reader * * @return Reader */ public function getReader() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9587', '%s is deprecated with no replacement', __METHOD__ ); return $this->reader; } /** * {@inheritDoc} */ public function isTransient($className) { $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className)); foreach ($classAnnotations as $annot) { if (isset($this->entityAnnotationClasses[get_class($annot)])) { return false; } } return true; } /** * Factory method for the Annotation Driver. * * @param mixed[]|string $paths * * @return AnnotationDriver */ public static function create($paths = [], ?AnnotationReader $reader = null) { if ($reader === null) { $reader = new AnnotationReader(); } return new self($reader, $paths); } } orm/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php 0000644 00000074434 15120025734 0017004 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingAttribute; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; use LogicException; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use function assert; use function class_exists; use function constant; use function defined; use function get_class; use function sprintf; use const PHP_VERSION_ID; class AttributeDriver extends CompatibilityAnnotationDriver { use ColocatedMappingDriver; private const ENTITY_ATTRIBUTE_CLASSES = [ Mapping\Entity::class => 1, Mapping\MappedSuperclass::class => 2, ]; /** * @deprecated override isTransient() instead of overriding this property * * @var array<class-string<MappingAttribute>, int> */ protected $entityAnnotationClasses = self::ENTITY_ATTRIBUTE_CLASSES; /** * The attribute reader. * * @internal this property will be private in 3.0 * * @var AttributeReader */ protected $reader; /** @param array<string> $paths */ public function __construct(array $paths) { if (PHP_VERSION_ID < 80000) { throw new LogicException(sprintf( 'The attribute metadata driver cannot be enabled on PHP 7. Please upgrade to PHP 8 or choose a different' . ' metadata driver.' )); } $this->reader = new AttributeReader(); $this->addPaths($paths); if ($this->entityAnnotationClasses !== self::ENTITY_ATTRIBUTE_CLASSES) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10204', 'Changing the value of %s::$entityAnnotationClasses is deprecated and will have no effect in Doctrine ORM 3.0.', self::class ); } } /** * Retrieve the current annotation reader * * @deprecated no replacement planned. * * @return AttributeReader */ public function getReader() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9587', '%s is deprecated with no replacement', __METHOD__ ); return $this->reader; } /** * {@inheritDoc} */ public function isTransient($className) { $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className)); foreach ($classAttributes as $a) { $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a; if (isset($this->entityAnnotationClasses[get_class($attr)])) { return false; } } return true; } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @template T of object */ public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void { $reflectionClass = $metadata->getReflectionClass() // this happens when running attribute driver in combination with // static reflection services. This is not the nicest fix ?? new ReflectionClass($metadata->name); $classAttributes = $this->reader->getClassAttributes($reflectionClass); // Evaluate Entity attribute if (isset($classAttributes[Mapping\Entity::class])) { $entityAttribute = $classAttributes[Mapping\Entity::class]; if ($entityAttribute->repositoryClass !== null) { $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass); } if ($entityAttribute->readOnly) { $metadata->markReadOnly(); } } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) { $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class]; $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass); $metadata->isMappedSuperclass = true; } elseif (isset($classAttributes[Mapping\Embeddable::class])) { $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } $primaryTable = []; if (isset($classAttributes[Mapping\Table::class])) { $tableAnnot = $classAttributes[Mapping\Table::class]; $primaryTable['name'] = $tableAnnot->name; $primaryTable['schema'] = $tableAnnot->schema; if ($tableAnnot->options) { $primaryTable['options'] = $tableAnnot->options; } } if (isset($classAttributes[Mapping\Index::class])) { foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) { $index = []; if (! empty($indexAnnot->columns)) { $index['columns'] = $indexAnnot->columns; } if (! empty($indexAnnot->fields)) { $index['fields'] = $indexAnnot->fields; } if ( isset($index['columns'], $index['fields']) || ( ! isset($index['columns']) && ! isset($index['fields']) ) ) { throw MappingException::invalidIndexConfiguration( $className, (string) ($indexAnnot->name ?? $idx) ); } if (! empty($indexAnnot->flags)) { $index['flags'] = $indexAnnot->flags; } if (! empty($indexAnnot->options)) { $index['options'] = $indexAnnot->options; } if (! empty($indexAnnot->name)) { $primaryTable['indexes'][$indexAnnot->name] = $index; } else { $primaryTable['indexes'][] = $index; } } } if (isset($classAttributes[Mapping\UniqueConstraint::class])) { foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) { $uniqueConstraint = []; if (! empty($uniqueConstraintAnnot->columns)) { $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns; } if (! empty($uniqueConstraintAnnot->fields)) { $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields; } if ( isset($uniqueConstraint['columns'], $uniqueConstraint['fields']) || ( ! isset($uniqueConstraint['columns']) && ! isset($uniqueConstraint['fields']) ) ) { throw MappingException::invalidUniqueConstraintConfiguration( $className, (string) ($uniqueConstraintAnnot->name ?? $idx) ); } if (! empty($uniqueConstraintAnnot->options)) { $uniqueConstraint['options'] = $uniqueConstraintAnnot->options; } if (! empty($uniqueConstraintAnnot->name)) { $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint; } else { $primaryTable['uniqueConstraints'][] = $uniqueConstraint; } } } $metadata->setPrimaryTable($primaryTable); // Evaluate #[Cache] attribute if (isset($classAttributes[Mapping\Cache::class])) { $cacheAttribute = $classAttributes[Mapping\Cache::class]; $cacheMap = [ 'region' => $cacheAttribute->region, 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), ]; $metadata->enableCache($cacheMap); } // Evaluate InheritanceType attribute if (isset($classAttributes[Mapping\InheritanceType::class])) { $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class]; $metadata->setInheritanceType( constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value) ); if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate DiscriminatorColumn attribute if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) { $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class]; $columnDef = [ 'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null, 'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string', 'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255, 'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null, 'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null, ]; if ($discrColumnAttribute->options) { $columnDef['options'] = (array) $discrColumnAttribute->options; } $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } // Evaluate DiscriminatorMap attribute if (isset($classAttributes[Mapping\DiscriminatorMap::class])) { $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class]; $metadata->setDiscriminatorMap($discrMapAttribute->value); } } } // Evaluate DoctrineChangeTrackingPolicy attribute if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) { $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class]; $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value)); } foreach ($reflectionClass->getProperties() as $property) { assert($property instanceof ReflectionProperty); if ( $metadata->isMappedSuperclass && ! $property->isPrivate() || $metadata->isInheritedField($property->name) || $metadata->isInheritedAssociation($property->name) || $metadata->isInheritedEmbeddedClass($property->name) ) { continue; } $mapping = []; $mapping['fieldName'] = $property->getName(); // Evaluate #[Cache] attribute $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class); if ($cacheAttribute !== null) { assert($cacheAttribute instanceof Mapping\Cache); $mapping['cache'] = $metadata->getAssociationCacheDefaults( $mapping['fieldName'], [ 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage), 'region' => $cacheAttribute->region, ] ); } // Check for JoinColumn/JoinColumns attributes $joinColumns = []; $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class); foreach ($joinColumnAttributes as $joinColumnAttribute) { $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute); } // Field can only be attributed with one of: // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class); $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class); $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class); $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class); $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class); $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class); if ($columnAttribute !== null) { $mapping = $this->columnToArray($property->getName(), $columnAttribute); if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { $mapping['id'] = true; } $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class); if ($generatedValueAttribute !== null) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy)); } if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); // Check for SequenceGenerator/TableGenerator definition $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class); $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class); if ($seqGeneratorAttribute !== null) { $metadata->setSequenceGeneratorDefinition( [ 'sequenceName' => $seqGeneratorAttribute->sequenceName, 'allocationSize' => $seqGeneratorAttribute->allocationSize, 'initialValue' => $seqGeneratorAttribute->initialValue, ] ); } elseif ($customGeneratorAttribute !== null) { $metadata->setCustomGeneratorDefinition( [ 'class' => $customGeneratorAttribute->class, ] ); } } elseif ($oneToOneAttribute !== null) { if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) { $mapping['id'] = true; } $mapping['targetEntity'] = $oneToOneAttribute->targetEntity; $mapping['joinColumns'] = $joinColumns; $mapping['mappedBy'] = $oneToOneAttribute->mappedBy; $mapping['inversedBy'] = $oneToOneAttribute->inversedBy; $mapping['cascade'] = $oneToOneAttribute->cascade; $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch); $metadata->mapOneToOne($mapping); } elseif ($oneToManyAttribute !== null) { $mapping['mappedBy'] = $oneToManyAttribute->mappedBy; $mapping['targetEntity'] = $oneToManyAttribute->targetEntity; $mapping['cascade'] = $oneToManyAttribute->cascade; $mapping['indexBy'] = $oneToManyAttribute->indexBy; $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch); $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); if ($orderByAttribute !== null) { $mapping['orderBy'] = $orderByAttribute->value; } $metadata->mapOneToMany($mapping); } elseif ($manyToOneAttribute !== null) { $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class); if ($idAttribute !== null) { $mapping['id'] = true; } $mapping['joinColumns'] = $joinColumns; $mapping['cascade'] = $manyToOneAttribute->cascade; $mapping['inversedBy'] = $manyToOneAttribute->inversedBy; $mapping['targetEntity'] = $manyToOneAttribute->targetEntity; $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch); $metadata->mapManyToOne($mapping); } elseif ($manyToManyAttribute !== null) { $joinTable = []; $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class); if ($joinTableAttribute !== null) { $joinTable = [ 'name' => $joinTableAttribute->name, 'schema' => $joinTableAttribute->schema, ]; if ($joinTableAttribute->options) { $joinTable['options'] = $joinTableAttribute->options; } foreach ($joinTableAttribute->joinColumns as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } } foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn); } foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn); } $mapping['joinTable'] = $joinTable; $mapping['targetEntity'] = $manyToManyAttribute->targetEntity; $mapping['mappedBy'] = $manyToManyAttribute->mappedBy; $mapping['inversedBy'] = $manyToManyAttribute->inversedBy; $mapping['cascade'] = $manyToManyAttribute->cascade; $mapping['indexBy'] = $manyToManyAttribute->indexBy; $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch); $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class); if ($orderByAttribute !== null) { $mapping['orderBy'] = $orderByAttribute->value; } $metadata->mapManyToMany($mapping); } elseif ($embeddedAttribute !== null) { $mapping['class'] = $embeddedAttribute->class; $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix; $metadata->mapEmbedded($mapping); } } // Evaluate AssociationOverrides attribute if (isset($classAttributes[Mapping\AssociationOverrides::class])) { $associationOverride = $classAttributes[Mapping\AssociationOverrides::class]; foreach ($associationOverride->overrides as $associationOverride) { $override = []; $fieldName = $associationOverride->name; // Check for JoinColumn/JoinColumns attributes if ($associationOverride->joinColumns) { $joinColumns = []; foreach ($associationOverride->joinColumns as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } $override['joinColumns'] = $joinColumns; } if ($associationOverride->inverseJoinColumns) { $joinColumns = []; foreach ($associationOverride->inverseJoinColumns as $joinColumn) { $joinColumns[] = $this->joinColumnToArray($joinColumn); } $override['inverseJoinColumns'] = $joinColumns; } // Check for JoinTable attributes if ($associationOverride->joinTable) { $joinTableAnnot = $associationOverride->joinTable; $joinTable = [ 'name' => $joinTableAnnot->name, 'schema' => $joinTableAnnot->schema, 'joinColumns' => $override['joinColumns'] ?? [], 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [], ]; unset($override['joinColumns'], $override['inverseJoinColumns']); $override['joinTable'] = $joinTable; } // Check for inversedBy if ($associationOverride->inversedBy) { $override['inversedBy'] = $associationOverride->inversedBy; } // Check for `fetch` if ($associationOverride->fetch) { $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch); } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate AttributeOverrides attribute if (isset($classAttributes[Mapping\AttributeOverrides::class])) { $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class]; foreach ($attributeOverridesAnnot->overrides as $attributeOverride) { $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column); $metadata->setAttributeOverride($attributeOverride->name, $mapping); } } // Evaluate EntityListeners attribute if (isset($classAttributes[Mapping\EntityListeners::class])) { $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class]; foreach ($entityListenersAttribute->value as $item) { $listenerClassName = $metadata->fullyQualifiedClassName($item); if (! class_exists($listenerClassName)) { throw MappingException::entityListenerClassNotFound($listenerClassName, $className); } $hasMapping = false; $listenerClass = new ReflectionClass($listenerClassName); foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { assert($method instanceof ReflectionMethod); // find method callbacks. $callbacks = $this->getMethodCallbacks($method); $hasMapping = $hasMapping ?: ! empty($callbacks); foreach ($callbacks as $value) { $metadata->addEntityListener($value[1], $listenerClassName, $value[0]); } } // Evaluate the listener using naming convention. if (! $hasMapping) { EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName); } } } // Evaluate #[HasLifecycleCallbacks] attribute if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) { foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { assert($method instanceof ReflectionMethod); foreach ($this->getMethodCallbacks($method) as $value) { $metadata->addLifecycleCallback($value[0], $value[1]); } } } } /** * Attempts to resolve the fetch mode. * * @param class-string $className The class name. * @param string $fetchMode The fetch mode. * * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata. * * @throws MappingException If the fetch mode is not valid. */ private function getFetchMode(string $className, string $fetchMode): int { if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) { throw MappingException::invalidFetchMode($className, $fetchMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode); } /** * Attempts to resolve the generated mode. * * @throws MappingException If the fetch mode is not valid. */ private function getGeneratedMode(string $generatedMode): int { if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) { throw MappingException::invalidGeneratedMode($generatedMode); } return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode); } /** * Parses the given method. * * @return list<array{string, string}> * @psalm-return list<array{string, (Events::*)}> */ private function getMethodCallbacks(ReflectionMethod $method): array { $callbacks = []; $attributes = $this->reader->getMethodAttributes($method); foreach ($attributes as $attribute) { if ($attribute instanceof Mapping\PrePersist) { $callbacks[] = [$method->name, Events::prePersist]; } if ($attribute instanceof Mapping\PostPersist) { $callbacks[] = [$method->name, Events::postPersist]; } if ($attribute instanceof Mapping\PreUpdate) { $callbacks[] = [$method->name, Events::preUpdate]; } if ($attribute instanceof Mapping\PostUpdate) { $callbacks[] = [$method->name, Events::postUpdate]; } if ($attribute instanceof Mapping\PreRemove) { $callbacks[] = [$method->name, Events::preRemove]; } if ($attribute instanceof Mapping\PostRemove) { $callbacks[] = [$method->name, Events::postRemove]; } if ($attribute instanceof Mapping\PostLoad) { $callbacks[] = [$method->name, Events::postLoad]; } if ($attribute instanceof Mapping\PreFlush) { $callbacks[] = [$method->name, Events::preFlush]; } } return $callbacks; } /** * Parse the given JoinColumn as array * * @param Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn * * @return mixed[] * @psalm-return array{ * name: string|null, * unique: bool, * nullable: bool, * onDelete: mixed, * columnDefinition: string|null, * referencedColumnName: string, * options?: array<string, mixed> * } */ private function joinColumnToArray($joinColumn): array { $mapping = [ 'name' => $joinColumn->name, 'unique' => $joinColumn->unique, 'nullable' => $joinColumn->nullable, 'onDelete' => $joinColumn->onDelete, 'columnDefinition' => $joinColumn->columnDefinition, 'referencedColumnName' => $joinColumn->referencedColumnName, ]; if ($joinColumn->options) { $mapping['options'] = $joinColumn->options; } return $mapping; } /** * Parse the given Column as array * * @return mixed[] * @psalm-return array{ * fieldName: string, * type: mixed, * scale: int, * length: int, * unique: bool, * nullable: bool, * precision: int, * enumType?: class-string, * options?: mixed[], * columnName?: string, * columnDefinition?: string * } */ private function columnToArray(string $fieldName, Mapping\Column $column): array { $mapping = [ 'fieldName' => $fieldName, 'type' => $column->type, 'scale' => $column->scale, 'length' => $column->length, 'unique' => $column->unique, 'nullable' => $column->nullable, 'precision' => $column->precision, ]; if ($column->options) { $mapping['options'] = $column->options; } if (isset($column->name)) { $mapping['columnName'] = $column->name; } if (isset($column->columnDefinition)) { $mapping['columnDefinition'] = $column->columnDefinition; } if ($column->updatable === false) { $mapping['notUpdatable'] = true; } if ($column->insertable === false) { $mapping['notInsertable'] = true; } if ($column->generated !== null) { $mapping['generated'] = $this->getGeneratedMode($column->generated); } if ($column->enumType) { $mapping['enumType'] = $column->enumType; } return $mapping; } } orm/lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php 0000644 00000010734 15120025734 0016744 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Attribute; use Doctrine\ORM\Mapping\Annotation; use LogicException; use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use function assert; use function is_string; use function is_subclass_of; use function sprintf; /** @internal */ final class AttributeReader { /** @var array<class-string<Annotation>,bool> */ private array $isRepeatableAttribute = []; /** * @psalm-return class-string-map<T, T|RepeatableAttributeCollection<T>> * * @template T of Annotation */ public function getClassAttributes(ReflectionClass $class): array { return $this->convertToAttributeInstances($class->getAttributes()); } /** * @return class-string-map<T, T|RepeatableAttributeCollection<T>> * * @template T of Annotation */ public function getMethodAttributes(ReflectionMethod $method): array { return $this->convertToAttributeInstances($method->getAttributes()); } /** * @return class-string-map<T, T|RepeatableAttributeCollection<T>> * * @template T of Annotation */ public function getPropertyAttributes(ReflectionProperty $property): array { return $this->convertToAttributeInstances($property->getAttributes()); } /** * @param class-string<T> $attributeName The name of the annotation. * * @return T|null * * @template T of Annotation */ public function getPropertyAttribute(ReflectionProperty $property, $attributeName) { if ($this->isRepeatable($attributeName)) { throw new LogicException(sprintf( 'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.', $attributeName )); } return $this->getPropertyAttributes($property)[$attributeName] ?? ($this->isRepeatable($attributeName) ? new RepeatableAttributeCollection() : null); } /** * @param class-string<T> $attributeName The name of the annotation. * * @return RepeatableAttributeCollection<T> * * @template T of Annotation */ public function getPropertyAttributeCollection( ReflectionProperty $property, string $attributeName ): RepeatableAttributeCollection { if (! $this->isRepeatable($attributeName)) { throw new LogicException(sprintf( 'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.', $attributeName )); } return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection(); } /** * @param array<ReflectionAttribute> $attributes * * @return class-string-map<T, T|RepeatableAttributeCollection<T>> * * @template T of Annotation */ private function convertToAttributeInstances(array $attributes): array { $instances = []; foreach ($attributes as $attribute) { $attributeName = $attribute->getName(); assert(is_string($attributeName)); // Make sure we only get Doctrine Attributes if (! is_subclass_of($attributeName, Annotation::class)) { continue; } $instance = $attribute->newInstance(); assert($instance instanceof Annotation); if ($this->isRepeatable($attributeName)) { if (! isset($instances[$attributeName])) { $instances[$attributeName] = new RepeatableAttributeCollection(); } $collection = $instances[$attributeName]; assert($collection instanceof RepeatableAttributeCollection); $collection[] = $instance; } else { $instances[$attributeName] = $instance; } } return $instances; } /** @param class-string<Annotation> $attributeClassName */ private function isRepeatable(string $attributeClassName): bool { if (isset($this->isRepeatableAttribute[$attributeClassName])) { return $this->isRepeatableAttribute[$attributeClassName]; } $reflectionClass = new ReflectionClass($attributeClassName); $attribute = $reflectionClass->getAttributes()[0]->newInstance(); return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0; } } orm/lib/Doctrine/ORM/Mapping/Driver/CompatibilityAnnotationDriver.php 0000644 00000001152 15120025734 0021670 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as PersistenceAnnotationDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use function class_exists; if (! class_exists(PersistenceAnnotationDriver::class)) { /** @internal This class will be removed in ORM 3.0. */ abstract class CompatibilityAnnotationDriver implements MappingDriver { } } else { /** @internal This class will be removed in ORM 3.0. */ abstract class CompatibilityAnnotationDriver extends PersistenceAnnotationDriver { } } orm/lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php 0000644 00000043766 15120025734 0016551 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\InflectorFactory; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use InvalidArgumentException; use function array_diff; use function array_keys; use function array_merge; use function assert; use function count; use function current; use function get_class; use function in_array; use function preg_replace; use function sort; use function strtolower; /** * The DatabaseDriver reverse engineers the mapping metadata from a database. * * @link www.doctrine-project.org */ class DatabaseDriver implements MappingDriver { /** * Replacement for {@see Types::ARRAY}. * * To be removed as soon as support for DBAL 3 is dropped. */ private const ARRAY = 'array'; /** * Replacement for {@see Types::OBJECT}. * * To be removed as soon as support for DBAL 3 is dropped. */ private const OBJECT = 'object'; /** * Replacement for {@see Types::JSON_ARRAY}. * * To be removed as soon as support for DBAL 2 is dropped. */ private const JSON_ARRAY = 'json_array'; /** @var AbstractSchemaManager */ private $sm; /** @var array<string,Table>|null */ private $tables = null; /** @var array<class-string, string> */ private $classToTableNames = []; /** @psalm-var array<string, Table> */ private $manyToManyTables = []; /** @var mixed[] */ private $classNamesForTables = []; /** @var mixed[] */ private $fieldNamesForColumns = []; /** * The namespace for the generated entities. * * @var string|null */ private $namespace; /** @var Inflector */ private $inflector; public function __construct(AbstractSchemaManager $schemaManager) { $this->sm = $schemaManager; $this->inflector = InflectorFactory::create()->build(); } /** * Set the namespace for the generated entities. * * @param string $namespace * * @return void */ public function setNamespace($namespace) { $this->namespace = $namespace; } /** * {@inheritDoc} */ public function isTransient($className) { return true; } /** * {@inheritDoc} */ public function getAllClassNames() { $this->reverseEngineerMappingFromDatabase(); return array_keys($this->classToTableNames); } /** * Sets class name for a table. * * @param string $tableName * @param string $className * * @return void */ public function setClassNameForTable($tableName, $className) { $this->classNamesForTables[$tableName] = $className; } /** * Sets field name for a column on a specific table. * * @param string $tableName * @param string $columnName * @param string $fieldName * * @return void */ public function setFieldNameForColumn($tableName, $columnName, $fieldName) { $this->fieldNamesForColumns[$tableName][$columnName] = $fieldName; } /** * Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager. * * @param Table[] $entityTables * @param Table[] $manyToManyTables * @psalm-param list<Table> $entityTables * @psalm-param list<Table> $manyToManyTables * * @return void */ public function setTables($entityTables, $manyToManyTables) { $this->tables = $this->manyToManyTables = $this->classToTableNames = []; foreach ($entityTables as $table) { $className = $this->getClassNameForTable($table->getName()); $this->classToTableNames[$className] = $table->getName(); $this->tables[$table->getName()] = $table; } foreach ($manyToManyTables as $table) { $this->manyToManyTables[$table->getName()] = $table; } } public function setInflector(Inflector $inflector): void { $this->inflector = $inflector; } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @template T of object */ public function loadMetadataForClass($className, PersistenceClassMetadata $metadata) { if (! $metadata instanceof ClassMetadata) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/249', 'Passing an instance of %s to %s is deprecated, please pass a %s instance instead.', get_class($metadata), __METHOD__, ClassMetadata::class ); } $this->reverseEngineerMappingFromDatabase(); if (! isset($this->classToTableNames[$className])) { throw new InvalidArgumentException('Unknown class ' . $className); } $tableName = $this->classToTableNames[$className]; $metadata->name = $className; $metadata->table['name'] = $tableName; $this->buildIndexes($metadata); $this->buildFieldMappings($metadata); $this->buildToOneAssociationMappings($metadata); foreach ($this->manyToManyTables as $manyTable) { foreach ($manyTable->getForeignKeys() as $foreignKey) { // foreign key maps to the table of the current entity, many to many association probably exists if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) { continue; } $myFk = $foreignKey; $otherFk = null; foreach ($manyTable->getForeignKeys() as $foreignKey) { if ($foreignKey !== $myFk) { $otherFk = $foreignKey; break; } } if (! $otherFk) { // the definition of this many to many table does not contain // enough foreign key information to continue reverse engineering. continue; } $localColumn = current($myFk->getLocalColumns()); $associationMapping = []; $associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true); $associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName()); if (current($manyTable->getColumns())->getName() === $localColumn) { $associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); $associationMapping['joinTable'] = [ 'name' => strtolower($manyTable->getName()), 'joinColumns' => [], 'inverseJoinColumns' => [], ]; $fkCols = $myFk->getForeignColumns(); $cols = $myFk->getLocalColumns(); for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { $associationMapping['joinTable']['joinColumns'][] = [ 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ]; } $fkCols = $otherFk->getForeignColumns(); $cols = $otherFk->getLocalColumns(); for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) { $associationMapping['joinTable']['inverseJoinColumns'][] = [ 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ]; } } else { $associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true); } $metadata->mapManyToMany($associationMapping); break; } } } /** @throws MappingException */ private function reverseEngineerMappingFromDatabase(): void { if ($this->tables !== null) { return; } $this->tables = $this->manyToManyTables = $this->classToTableNames = []; foreach ($this->sm->listTables() as $table) { $tableName = $table->getName(); $foreignKeys = $table->getForeignKeys(); $allForeignKeyColumns = []; foreach ($foreignKeys as $foreignKey) { $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); } $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { throw new MappingException( 'Table ' . $tableName . ' has no primary key. Doctrine does not ' . "support reverse engineering from tables that don't have a primary key." ); } $pkColumns = $primaryKey->getColumns(); sort($pkColumns); sort($allForeignKeyColumns); if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) { $this->manyToManyTables[$tableName] = $table; } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. $className = $this->getClassNameForTable($tableName); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } } } /** * Build indexes from a class metadata. */ private function buildIndexes(ClassMetadataInfo $metadata): void { $tableName = $metadata->table['name']; $indexes = $this->tables[$tableName]->getIndexes(); foreach ($indexes as $index) { if ($index->isPrimary()) { continue; } $indexName = $index->getName(); $indexColumns = $index->getColumns(); $constraintType = $index->isUnique() ? 'uniqueConstraints' : 'indexes'; $metadata->table[$constraintType][$indexName]['columns'] = $indexColumns; } } /** * Build field mapping from class metadata. */ private function buildFieldMappings(ClassMetadataInfo $metadata): void { $tableName = $metadata->table['name']; $columns = $this->tables[$tableName]->getColumns(); $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); $foreignKeys = $this->tables[$tableName]->getForeignKeys(); $allForeignKeys = []; foreach ($foreignKeys as $foreignKey) { $allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns()); } $ids = []; $fieldMappings = []; foreach ($columns as $column) { if (in_array($column->getName(), $allForeignKeys, true)) { continue; } $fieldMapping = $this->buildFieldMapping($tableName, $column); if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) { $fieldMapping['id'] = true; $ids[] = $fieldMapping; } $fieldMappings[] = $fieldMapping; } // We need to check for the columns here, because we might have associations as id as well. if ($ids && count($primaryKeys) === 1) { $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); } foreach ($fieldMappings as $fieldMapping) { $metadata->mapField($fieldMapping); } } /** * Build field mapping from a schema column definition * * @return mixed[] * @psalm-return array{ * fieldName: string, * columnName: string, * type: string, * nullable: bool, * options?: array{ * unsigned?: bool, * fixed?: bool, * comment?: string, * default?: string * }, * precision?: int, * scale?: int, * length?: int|null * } */ private function buildFieldMapping(string $tableName, Column $column): array { $fieldMapping = [ 'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false), 'columnName' => $column->getName(), 'type' => Type::getTypeRegistry()->lookupName($column->getType()), 'nullable' => ! $column->getNotnull(), ]; // Type specific elements switch ($fieldMapping['type']) { case self::ARRAY: case Types::BLOB: case Types::GUID: case self::JSON_ARRAY: case self::OBJECT: case Types::SIMPLE_ARRAY: case Types::STRING: case Types::TEXT: $fieldMapping['length'] = $column->getLength(); $fieldMapping['options']['fixed'] = $column->getFixed(); break; case Types::DECIMAL: case Types::FLOAT: $fieldMapping['precision'] = $column->getPrecision(); $fieldMapping['scale'] = $column->getScale(); break; case Types::INTEGER: case Types::BIGINT: case Types::SMALLINT: $fieldMapping['options']['unsigned'] = $column->getUnsigned(); break; } // Comment $comment = $column->getComment(); if ($comment !== null) { $fieldMapping['options']['comment'] = $comment; } // Default $default = $column->getDefault(); if ($default !== null) { $fieldMapping['options']['default'] = $default; } return $fieldMapping; } /** * Build to one (one to one, many to one) association mapping from class metadata. * * @return void */ private function buildToOneAssociationMappings(ClassMetadataInfo $metadata) { assert($this->tables !== null); $tableName = $metadata->table['name']; $primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]); $foreignKeys = $this->tables[$tableName]->getForeignKeys(); foreach ($foreignKeys as $foreignKey) { $foreignTableName = $foreignKey->getForeignTableName(); $fkColumns = $foreignKey->getLocalColumns(); $fkForeignColumns = $foreignKey->getForeignColumns(); $localColumn = current($fkColumns); $associationMapping = [ 'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true), 'targetEntity' => $this->getClassNameForTable($foreignTableName), ]; if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) { $associationMapping['fieldName'] .= '2'; // "foo" => "foo2" } if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) { $associationMapping['id'] = true; } for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) { $associationMapping['joinColumns'][] = [ 'name' => $fkColumns[$i], 'referencedColumnName' => $fkForeignColumns[$i], ]; } // Here we need to check if $fkColumns are the same as $primaryKeys if (! array_diff($fkColumns, $primaryKeys)) { $metadata->mapOneToOne($associationMapping); } else { $metadata->mapManyToOne($associationMapping); } } } /** * Retrieve schema table definition primary keys. * * @return string[] */ private function getTablePrimaryKeys(Table $table): array { try { return $table->getPrimaryKey()->getColumns(); } catch (SchemaException $e) { // Do nothing } return []; } /** * Returns the mapped class name for a table if it exists. Otherwise return "classified" version. * * @psalm-return class-string */ private function getClassNameForTable(string $tableName): string { if (isset($this->classNamesForTables[$tableName])) { return $this->namespace . $this->classNamesForTables[$tableName]; } return $this->namespace . $this->inflector->classify(strtolower($tableName)); } /** * Return the mapped field name for a column, if it exists. Otherwise return camelized version. * * @param bool $fk Whether the column is a foreignkey or not. */ private function getFieldNameForColumn( string $tableName, string $columnName, bool $fk = false ): string { if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) { return $this->fieldNamesForColumns[$tableName][$columnName]; } $columnName = strtolower($columnName); // Replace _id if it is a foreignkey column if ($fk) { $columnName = preg_replace('/_id$/', '', $columnName); } return $this->inflector->camelize($columnName); } } orm/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php 0000644 00000000502 15120025734 0016044 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Persistence\Mapping\Driver\MappingDriverChain instead */ class DriverChain extends MappingDriverChain { } orm/lib/Doctrine/ORM/Mapping/Driver/PHPDriver.php 0000644 00000001410 15120025734 0015450 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Deprecations\Deprecation; use Doctrine\Persistence\Mapping\Driver\FileLocator; use Doctrine\Persistence\Mapping\Driver\PHPDriver as CommonPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed, use StaticPHPDriver or other mapping drivers instead. */ class PHPDriver extends CommonPHPDriver { /** @param string|string[]|FileLocator $locator */ public function __construct($locator) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9277', 'PHPDriver is deprecated, use StaticPHPDriver or other mapping drivers instead.' ); parent::__construct($locator); } } orm/lib/Doctrine/ORM/Mapping/Driver/RepeatableAttributeCollection.php 0000644 00000000420 15120025734 0021611 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use ArrayObject; use Doctrine\ORM\Mapping\Annotation; /** * @template-extends ArrayObject<int, T> * @template T of Annotation */ final class RepeatableAttributeCollection extends ArrayObject { } orm/lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php 0000644 00000001224 15120025734 0017572 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; /** * XmlDriver that additionally looks for mapping information in a global file. */ class SimplifiedXmlDriver extends XmlDriver { public const DEFAULT_FILE_EXTENSION = '.orm.xml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension, $isXsdValidationEnabled); } } orm/lib/Doctrine/ORM/Mapping/Driver/SimplifiedYamlDriver.php 0000644 00000001262 15120025734 0017736 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; /** * YamlDriver that additionally looks for mapping information in a global file. * * @deprecated This class is being removed from the ORM and won't have any replacement */ class SimplifiedYamlDriver extends YamlDriver { public const DEFAULT_FILE_EXTENSION = '.orm.yml'; /** * {@inheritDoc} */ public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION) { $locator = new SymfonyFileLocator((array) $prefixes, $fileExtension); parent::__construct($locator, $fileExtension); } } orm/lib/Doctrine/ORM/Mapping/Driver/StaticPHPDriver.php 0000644 00000000534 15120025734 0016626 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver as CommonStaticPHPDriver; /** * {@inheritDoc} * * @deprecated this driver will be removed. Use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver instead */ class StaticPHPDriver extends CommonStaticPHPDriver { } orm/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php 0000644 00000122323 15120025734 0015570 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Collections\Criteria; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\FileDriver; use DOMDocument; use InvalidArgumentException; use LogicException; use SimpleXMLElement; use function assert; use function constant; use function count; use function defined; use function explode; use function extension_loaded; use function file_get_contents; use function in_array; use function libxml_clear_errors; use function libxml_get_errors; use function libxml_use_internal_errors; use function simplexml_load_string; use function sprintf; use function str_replace; use function strtoupper; /** * XmlDriver is a metadata driver that enables mapping through XML files. * * @link www.doctrine-project.org */ class XmlDriver extends FileDriver { public const DEFAULT_FILE_EXTENSION = '.dcm.xml'; /** @var bool */ private $isXsdValidationEnabled; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false) { if (! extension_loaded('simplexml')) { throw new LogicException(sprintf( 'The XML metadata driver cannot be enabled because the SimpleXML PHP extension is missing.' . ' Please configure PHP with SimpleXML or choose a different metadata driver.' )); } if (! $isXsdValidationEnabled) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/6728', sprintf( 'Using XML mapping driver with XSD validation disabled is deprecated' . ' and will not be supported in Doctrine ORM 3.0.' ) ); } if ($isXsdValidationEnabled && ! extension_loaded('dom')) { throw new LogicException(sprintf( 'XSD validation cannot be enabled because the DOM extension is missing.' )); } $this->isXsdValidationEnabled = $isXsdValidationEnabled; parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @template T of object */ public function loadMetadataForClass($className, PersistenceClassMetadata $metadata) { $xmlRoot = $this->getElement($className); assert($xmlRoot instanceof SimpleXMLElement); if ($xmlRoot->getName() === 'entity') { if (isset($xmlRoot['repository-class'])) { $metadata->setCustomRepositoryClass((string) $xmlRoot['repository-class']); } if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) { $metadata->markReadOnly(); } } elseif ($xmlRoot->getName() === 'mapped-superclass') { $metadata->setCustomRepositoryClass( isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null ); $metadata->isMappedSuperclass = true; } elseif ($xmlRoot->getName() === 'embeddable') { $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate <entity...> attributes $primaryTable = []; if (isset($xmlRoot['table'])) { $primaryTable['name'] = (string) $xmlRoot['table']; } if (isset($xmlRoot['schema'])) { $primaryTable['schema'] = (string) $xmlRoot['schema']; } $metadata->setPrimaryTable($primaryTable); // Evaluate second level cache if (isset($xmlRoot->cache)) { $metadata->enableCache($this->cacheToArray($xmlRoot->cache)); } // Evaluate named queries if (isset($xmlRoot->{'named-queries'})) { foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) { $metadata->addNamedQuery( [ 'name' => (string) $namedQueryElement['name'], 'query' => (string) $namedQueryElement['query'], ] ); } } // Evaluate native named queries if (isset($xmlRoot->{'named-native-queries'})) { foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) { $metadata->addNamedNativeQuery( [ 'name' => isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null, 'query' => isset($nativeQueryElement->query) ? (string) $nativeQueryElement->query : null, 'resultClass' => isset($nativeQueryElement['result-class']) ? (string) $nativeQueryElement['result-class'] : null, 'resultSetMapping' => isset($nativeQueryElement['result-set-mapping']) ? (string) $nativeQueryElement['result-set-mapping'] : null, ] ); } } // Evaluate sql result set mapping if (isset($xmlRoot->{'sql-result-set-mappings'})) { foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) { $entities = []; $columns = []; foreach ($rsmElement as $entityElement) { //<entity-result/> if (isset($entityElement['entity-class'])) { $entityResult = [ 'fields' => [], 'entityClass' => (string) $entityElement['entity-class'], 'discriminatorColumn' => isset($entityElement['discriminator-column']) ? (string) $entityElement['discriminator-column'] : null, ]; foreach ($entityElement as $fieldElement) { $entityResult['fields'][] = [ 'name' => isset($fieldElement['name']) ? (string) $fieldElement['name'] : null, 'column' => isset($fieldElement['column']) ? (string) $fieldElement['column'] : null, ]; } $entities[] = $entityResult; } //<column-result/> if (isset($entityElement['name'])) { $columns[] = [ 'name' => (string) $entityElement['name'], ]; } } $metadata->addSqlResultSetMapping( [ 'name' => (string) $rsmElement['name'], 'entities' => $entities, 'columns' => $columns, ] ); } } if (isset($xmlRoot['inheritance-type'])) { $inheritanceType = (string) $xmlRoot['inheritance-type']; $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType)); if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate <discriminator-column...> if (isset($xmlRoot->{'discriminator-column'})) { $discrColumn = $xmlRoot->{'discriminator-column'}; $columnDef = [ 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, 'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null, 'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null, ]; if (isset($discrColumn['options'])) { $columnDef['options'] = $this->parseOptions($discrColumn['options']->children()); } $metadata->setDiscriminatorColumn($columnDef); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } // Evaluate <discriminator-map...> if (isset($xmlRoot->{'discriminator-map'})) { $map = []; foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) { $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class']; } $metadata->setDiscriminatorMap($map); } } } // Evaluate <change-tracking-policy...> if (isset($xmlRoot['change-tracking-policy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper((string) $xmlRoot['change-tracking-policy']))); } // Evaluate <indexes...> if (isset($xmlRoot->indexes)) { $metadata->table['indexes'] = []; foreach ($xmlRoot->indexes->index as $indexXml) { $index = []; if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) { $index['columns'] = explode(',', (string) $indexXml['columns']); } if (isset($indexXml['fields'])) { $index['fields'] = explode(',', (string) $indexXml['fields']); } if ( isset($index['columns'], $index['fields']) || ( ! isset($index['columns']) && ! isset($index['fields']) ) ) { throw MappingException::invalidIndexConfiguration( $className, (string) ($indexXml['name'] ?? count($metadata->table['indexes'])) ); } if (isset($indexXml['flags'])) { $index['flags'] = explode(',', (string) $indexXml['flags']); } if (isset($indexXml->options)) { $index['options'] = $this->parseOptions($indexXml->options->children()); } if (isset($indexXml['name'])) { $metadata->table['indexes'][(string) $indexXml['name']] = $index; } else { $metadata->table['indexes'][] = $index; } } } // Evaluate <unique-constraints..> if (isset($xmlRoot->{'unique-constraints'})) { $metadata->table['uniqueConstraints'] = []; foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) { $unique = []; if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) { $unique['columns'] = explode(',', (string) $uniqueXml['columns']); } if (isset($uniqueXml['fields'])) { $unique['fields'] = explode(',', (string) $uniqueXml['fields']); } if ( isset($unique['columns'], $unique['fields']) || ( ! isset($unique['columns']) && ! isset($unique['fields']) ) ) { throw MappingException::invalidUniqueConstraintConfiguration( $className, (string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints'])) ); } if (isset($uniqueXml->options)) { $unique['options'] = $this->parseOptions($uniqueXml->options->children()); } if (isset($uniqueXml['name'])) { $metadata->table['uniqueConstraints'][(string) $uniqueXml['name']] = $unique; } else { $metadata->table['uniqueConstraints'][] = $unique; } } } if (isset($xmlRoot->options)) { $metadata->table['options'] = $this->parseOptions($xmlRoot->options->children()); } // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception $mappings = []; // Evaluate <field ...> mappings if (isset($xmlRoot->field)) { foreach ($xmlRoot->field as $fieldMapping) { $mapping = $this->columnToArray($fieldMapping); if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } if (isset($xmlRoot->embedded)) { foreach ($xmlRoot->embedded as $embeddedMapping) { $columnPrefix = isset($embeddedMapping['column-prefix']) ? (string) $embeddedMapping['column-prefix'] : null; $useColumnPrefix = isset($embeddedMapping['use-column-prefix']) ? $this->evaluateBoolean($embeddedMapping['use-column-prefix']) : true; $mapping = [ 'fieldName' => (string) $embeddedMapping['name'], 'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, 'columnPrefix' => $useColumnPrefix ? $columnPrefix : false, ]; $metadata->mapEmbedded($mapping); } } foreach ($mappings as $mapping) { if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); } $metadata->mapField($mapping); } // Evaluate <id ...> mappings $associationIds = []; foreach ($xmlRoot->id as $idElement) { if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) { $associationIds[(string) $idElement['name']] = true; continue; } $mapping = [ 'id' => true, 'fieldName' => (string) $idElement['name'], ]; if (isset($idElement['type'])) { $mapping['type'] = (string) $idElement['type']; } if (isset($idElement['length'])) { $mapping['length'] = (int) $idElement['length']; } if (isset($idElement['column'])) { $mapping['columnName'] = (string) $idElement['column']; } if (isset($idElement['column-definition'])) { $mapping['columnDefinition'] = (string) $idElement['column-definition']; } if (isset($idElement->options)) { $mapping['options'] = $this->parseOptions($idElement->options->children()); } $metadata->mapField($mapping); if (isset($idElement->generator)) { $strategy = isset($idElement->generator['strategy']) ? (string) $idElement->generator['strategy'] : 'AUTO'; $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $strategy)); } // Check for SequenceGenerator/TableGenerator definition if (isset($idElement->{'sequence-generator'})) { $seqGenerator = $idElement->{'sequence-generator'}; $metadata->setSequenceGeneratorDefinition( [ 'sequenceName' => (string) $seqGenerator['sequence-name'], 'allocationSize' => (string) $seqGenerator['allocation-size'], 'initialValue' => (string) $seqGenerator['initial-value'], ] ); } elseif (isset($idElement->{'custom-id-generator'})) { $customGenerator = $idElement->{'custom-id-generator'}; $metadata->setCustomGeneratorDefinition( [ 'class' => (string) $customGenerator['class'], ] ); } } // Evaluate <one-to-one ...> mappings if (isset($xmlRoot->{'one-to-one'})) { foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) { $mapping = [ 'fieldName' => (string) $oneToOneElement['field'], ]; if (isset($oneToOneElement['target-entity'])) { $mapping['targetEntity'] = (string) $oneToOneElement['target-entity']; } if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToOneElement['fetch']); } if (isset($oneToOneElement['mapped-by'])) { $mapping['mappedBy'] = (string) $oneToOneElement['mapped-by']; } else { if (isset($oneToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string) $oneToOneElement['inversed-by']; } $joinColumns = []; if (isset($oneToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'}); } elseif (isset($oneToOneElement->{'join-columns'})) { foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement->cascade)) { $mapping['cascade'] = $this->getCascadeMappings($oneToOneElement->cascade); } if (isset($oneToOneElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']); } // Evaluate second level cache if (isset($oneToOneElement->cache)) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache)); } $metadata->mapOneToOne($mapping); } } // Evaluate <one-to-many ...> mappings if (isset($xmlRoot->{'one-to-many'})) { foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) { $mapping = [ 'fieldName' => (string) $oneToManyElement['field'], 'mappedBy' => (string) $oneToManyElement['mapped-by'], ]; if (isset($oneToManyElement['target-entity'])) { $mapping['targetEntity'] = (string) $oneToManyElement['target-entity']; } if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']); } if (isset($oneToManyElement->cascade)) { $mapping['cascade'] = $this->getCascadeMappings($oneToManyElement->cascade); } if (isset($oneToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']); } if (isset($oneToManyElement->{'order-by'})) { $orderBy = []; foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) ? (string) $orderByField['direction'] : Criteria::ASC; } $mapping['orderBy'] = $orderBy; } if (isset($oneToManyElement['index-by'])) { $mapping['indexBy'] = (string) $oneToManyElement['index-by']; } elseif (isset($oneToManyElement->{'index-by'})) { throw new InvalidArgumentException('<index-by /> is not a valid tag'); } // Evaluate second level cache if (isset($oneToManyElement->cache)) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache)); } $metadata->mapOneToMany($mapping); } } // Evaluate <many-to-one ...> mappings if (isset($xmlRoot->{'many-to-one'})) { foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) { $mapping = [ 'fieldName' => (string) $manyToOneElement['field'], ]; if (isset($manyToOneElement['target-entity'])) { $mapping['targetEntity'] = (string) $manyToOneElement['target-entity']; } if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToOneElement['fetch']); } if (isset($manyToOneElement['inversed-by'])) { $mapping['inversedBy'] = (string) $manyToOneElement['inversed-by']; } $joinColumns = []; if (isset($manyToOneElement->{'join-column'})) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'}); } elseif (isset($manyToOneElement->{'join-columns'})) { foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement->cascade)) { $mapping['cascade'] = $this->getCascadeMappings($manyToOneElement->cascade); } // Evaluate second level cache if (isset($manyToOneElement->cache)) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache)); } $metadata->mapManyToOne($mapping); } } // Evaluate <many-to-many ...> mappings if (isset($xmlRoot->{'many-to-many'})) { foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) { $mapping = [ 'fieldName' => (string) $manyToManyElement['field'], ]; if (isset($manyToManyElement['target-entity'])) { $mapping['targetEntity'] = (string) $manyToManyElement['target-entity']; } if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']); } if (isset($manyToManyElement['orphan-removal'])) { $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']); } if (isset($manyToManyElement['mapped-by'])) { $mapping['mappedBy'] = (string) $manyToManyElement['mapped-by']; } elseif (isset($manyToManyElement->{'join-table'})) { if (isset($manyToManyElement['inversed-by'])) { $mapping['inversedBy'] = (string) $manyToManyElement['inversed-by']; } $joinTableElement = $manyToManyElement->{'join-table'}; $joinTable = [ 'name' => (string) $joinTableElement['name'], ]; if (isset($joinTableElement['schema'])) { $joinTable['schema'] = (string) $joinTableElement['schema']; } if (isset($joinTableElement->options)) { $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); } foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement->cascade)) { $mapping['cascade'] = $this->getCascadeMappings($manyToManyElement->cascade); } if (isset($manyToManyElement->{'order-by'})) { $orderBy = []; foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) { $orderBy[(string) $orderByField['name']] = isset($orderByField['direction']) ? (string) $orderByField['direction'] : Criteria::ASC; } $mapping['orderBy'] = $orderBy; } if (isset($manyToManyElement['index-by'])) { $mapping['indexBy'] = (string) $manyToManyElement['index-by']; } elseif (isset($manyToManyElement->{'index-by'})) { throw new InvalidArgumentException('<index-by /> is not a valid tag'); } // Evaluate second level cache if (isset($manyToManyElement->cache)) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache)); } $metadata->mapManyToMany($mapping); } } // Evaluate association-overrides if (isset($xmlRoot->{'attribute-overrides'})) { foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; foreach ($overrideElement->field as $field) { $mapping = $this->columnToArray($field); $mapping['fieldName'] = $fieldName; $metadata->setAttributeOverride($fieldName, $mapping); } } } // Evaluate association-overrides if (isset($xmlRoot->{'association-overrides'})) { foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) { $fieldName = (string) $overrideElement['name']; $override = []; // Check for join-columns if (isset($overrideElement->{'join-columns'})) { $joinColumns = []; foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for join-table if ($overrideElement->{'join-table'}) { $joinTable = null; $joinTableElement = $overrideElement->{'join-table'}; $joinTable = [ 'name' => (string) $joinTableElement['name'], 'schema' => (string) $joinTableElement['schema'], ]; if (isset($joinTableElement->options)) { $joinTable['options'] = $this->parseOptions($joinTableElement->options->children()); } if (isset($joinTableElement->{'join-columns'})) { foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } if (isset($joinTableElement->{'inverse-join-columns'})) { foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) { $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } $override['joinTable'] = $joinTable; } // Check for inversed-by if (isset($overrideElement->{'inversed-by'})) { $override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name']; } // Check for `fetch` if (isset($overrideElement['fetch'])) { $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . (string) $overrideElement['fetch']); } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate <lifecycle-callbacks...> if (isset($xmlRoot->{'lifecycle-callbacks'})) { foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) { $metadata->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string) $lifecycleCallback['type'])); } } // Evaluate entity listener if (isset($xmlRoot->{'entity-listeners'})) { foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) { $className = (string) $listenerElement['class']; // Evaluate the listener using naming convention. if ($listenerElement->count() === 0) { EntityListenerBuilder::bindEntityListener($metadata, $className); continue; } foreach ($listenerElement as $callbackElement) { $eventName = (string) $callbackElement['type']; $methodName = (string) $callbackElement['method']; $metadata->addEntityListener($eventName, $className, $methodName); } } } } /** * Parses (nested) option elements. * * @param SimpleXMLElement $options The XML element. * * @return mixed[] The options array. * @psalm-return array<int|string, array<int|string, mixed|string>|bool|string> */ private function parseOptions(SimpleXMLElement $options): array { $array = []; foreach ($options as $option) { if ($option->count()) { $value = $this->parseOptions($option->children()); } else { $value = (string) $option; } $attributes = $option->attributes(); if (isset($attributes->name)) { $nameAttribute = (string) $attributes->name; $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true) ? $this->evaluateBoolean($value) : $value; } else { $array[] = $value; } } return $array; } /** * Constructs a joinColumn mapping array based on the information * found in the given SimpleXMLElement. * * @param SimpleXMLElement $joinColumnElement The XML element. * * @return mixed[] The mapping array. * @psalm-return array{ * name: string, * referencedColumnName: string, * unique?: bool, * nullable?: bool, * onDelete?: string, * columnDefinition?: string, * options?: mixed[] * } */ private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array { $joinColumn = [ 'name' => (string) $joinColumnElement['name'], 'referencedColumnName' => (string) $joinColumnElement['referenced-column-name'], ]; if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']); } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']); } if (isset($joinColumnElement['on-delete'])) { $joinColumn['onDelete'] = (string) $joinColumnElement['on-delete']; } if (isset($joinColumnElement['column-definition'])) { $joinColumn['columnDefinition'] = (string) $joinColumnElement['column-definition']; } if (isset($joinColumnElement['options'])) { $joinColumn['options'] = $this->parseOptions($joinColumnElement['options']->children()); } return $joinColumn; } /** * Parses the given field as array. * * @return mixed[] * @psalm-return array{ * fieldName: string, * type?: string, * columnName?: string, * length?: int, * precision?: int, * scale?: int, * unique?: bool, * nullable?: bool, * notInsertable?: bool, * notUpdatable?: bool, * enumType?: string, * version?: bool, * columnDefinition?: string, * options?: array * } */ private function columnToArray(SimpleXMLElement $fieldMapping): array { $mapping = [ 'fieldName' => (string) $fieldMapping['name'], ]; if (isset($fieldMapping['type'])) { $mapping['type'] = (string) $fieldMapping['type']; } if (isset($fieldMapping['column'])) { $mapping['columnName'] = (string) $fieldMapping['column']; } if (isset($fieldMapping['length'])) { $mapping['length'] = (int) $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $mapping['precision'] = (int) $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $mapping['scale'] = (int) $fieldMapping['scale']; } if (isset($fieldMapping['unique'])) { $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']); } if (isset($fieldMapping['nullable'])) { $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); } if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) { $mapping['notInsertable'] = true; } if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) { $mapping['notUpdatable'] = true; } if (isset($fieldMapping['generated'])) { $mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']); } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); } if (isset($fieldMapping['column-definition'])) { $mapping['columnDefinition'] = (string) $fieldMapping['column-definition']; } if (isset($fieldMapping['enum-type'])) { $mapping['enumType'] = (string) $fieldMapping['enum-type']; } if (isset($fieldMapping->options)) { $mapping['options'] = $this->parseOptions($fieldMapping->options->children()); } return $mapping; } /** * Parse / Normalize the cache configuration * * @return mixed[] * @psalm-return array{usage: int|null, region?: string} */ private function cacheToArray(SimpleXMLElement $cacheMapping): array { $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null; $usage = isset($cacheMapping['usage']) ? strtoupper((string) $cacheMapping['usage']) : null; if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) { throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage)); } if ($usage) { $usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage); } return [ 'usage' => $usage, 'region' => $region, ]; } /** * Gathers a list of cascade options found in the given cascade element. * * @param SimpleXMLElement $cascadeElement The cascade element. * * @return string[] The list of cascade options. * @psalm-return list<string> */ private function getCascadeMappings(SimpleXMLElement $cascadeElement): array { $cascades = []; foreach ($cascadeElement->children() as $action) { // According to the JPA specifications, XML uses "cascade-persist" // instead of "persist". Here, both variations // are supported because YAML, Annotation and Attribute use "persist" // and we want to make sure that this driver doesn't need to know // anything about the supported cascading actions $cascades[] = str_replace('cascade-', '', $action->getName()); } return $cascades; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { $this->validateMapping($file); $result = []; // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577 $xmlElement = simplexml_load_string(file_get_contents($file)); assert($xmlElement !== false); if (isset($xmlElement->entity)) { foreach ($xmlElement->entity as $entityElement) { /** @psalm-var class-string */ $entityName = (string) $entityElement['name']; $result[$entityName] = $entityElement; } } elseif (isset($xmlElement->{'mapped-superclass'})) { foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) { /** @psalm-var class-string */ $className = (string) $mappedSuperClass['name']; $result[$className] = $mappedSuperClass; } } elseif (isset($xmlElement->embeddable)) { foreach ($xmlElement->embeddable as $embeddableElement) { /** @psalm-var class-string */ $embeddableName = (string) $embeddableElement['name']; $result[$embeddableName] = $embeddableElement; } } return $result; } private function validateMapping(string $file): void { if (! $this->isXsdValidationEnabled) { return; } $backedUpErrorSetting = libxml_use_internal_errors(true); try { $document = new DOMDocument(); $document->load($file); if (! $document->schemaValidate(__DIR__ . '/../../../../../doctrine-mapping.xsd')) { throw MappingException::fromLibXmlErrors(libxml_get_errors()); } } finally { libxml_clear_errors(); libxml_use_internal_errors($backedUpErrorSetting); } } /** * @param mixed $element * * @return bool */ protected function evaluateBoolean($element) { $flag = (string) $element; return $flag === 'true' || $flag === '1'; } } orm/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php 0000644 00000106750 15120025734 0015740 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\FileDriver; use InvalidArgumentException; use LogicException; use Symfony\Component\Yaml\Yaml; use function array_map; use function class_exists; use function constant; use function defined; use function explode; use function file_get_contents; use function is_array; use function is_string; use function sprintf; use function strlen; use function strtoupper; use function substr; /** * The YamlDriver reads the mapping metadata from yaml schema files. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement */ class YamlDriver extends FileDriver { public const DEFAULT_FILE_EXTENSION = '.dcm.yml'; /** * {@inheritDoc} */ public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8465', 'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.' ); if (! class_exists(Yaml::class)) { throw new LogicException(sprintf( 'The YAML metadata driver cannot be enabled because the "symfony/yaml" library' . ' is not installed. Please run "composer require symfony/yaml" or choose a different' . ' metadata driver.' )); } parent::__construct($locator, $fileExtension); } /** * {@inheritDoc} * * @psalm-param class-string<T> $className * @psalm-param ClassMetadata<T> $metadata * * @template T of object */ public function loadMetadataForClass($className, PersistenceClassMetadata $metadata) { $element = $this->getElement($className); if ($element['type'] === 'entity') { if (isset($element['repositoryClass'])) { $metadata->setCustomRepositoryClass($element['repositoryClass']); } if (isset($element['readOnly']) && $element['readOnly'] === true) { $metadata->markReadOnly(); } } elseif ($element['type'] === 'mappedSuperclass') { $metadata->setCustomRepositoryClass( $element['repositoryClass'] ?? null ); $metadata->isMappedSuperclass = true; } elseif ($element['type'] === 'embeddable') { $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } // Evaluate root level properties $primaryTable = []; if (isset($element['table'])) { $primaryTable['name'] = $element['table']; } if (isset($element['schema'])) { $primaryTable['schema'] = $element['schema']; } // Evaluate second level cache if (isset($element['cache'])) { $metadata->enableCache($this->cacheToArray($element['cache'])); } $metadata->setPrimaryTable($primaryTable); // Evaluate named queries if (isset($element['namedQueries'])) { foreach ($element['namedQueries'] as $name => $queryMapping) { if (is_string($queryMapping)) { $queryMapping = ['query' => $queryMapping]; } if (! isset($queryMapping['name'])) { $queryMapping['name'] = $name; } $metadata->addNamedQuery($queryMapping); } } // Evaluate named native queries if (isset($element['namedNativeQueries'])) { foreach ($element['namedNativeQueries'] as $name => $mappingElement) { if (! isset($mappingElement['name'])) { $mappingElement['name'] = $name; } $metadata->addNamedNativeQuery( [ 'name' => $mappingElement['name'], 'query' => $mappingElement['query'] ?? null, 'resultClass' => $mappingElement['resultClass'] ?? null, 'resultSetMapping' => $mappingElement['resultSetMapping'] ?? null, ] ); } } // Evaluate sql result set mappings if (isset($element['sqlResultSetMappings'])) { foreach ($element['sqlResultSetMappings'] as $name => $resultSetMapping) { if (! isset($resultSetMapping['name'])) { $resultSetMapping['name'] = $name; } $entities = []; $columns = []; if (isset($resultSetMapping['entityResult'])) { foreach ($resultSetMapping['entityResult'] as $entityResultElement) { $entityResult = [ 'fields' => [], 'entityClass' => $entityResultElement['entityClass'] ?? null, 'discriminatorColumn' => $entityResultElement['discriminatorColumn'] ?? null, ]; if (isset($entityResultElement['fieldResult'])) { foreach ($entityResultElement['fieldResult'] as $fieldResultElement) { $entityResult['fields'][] = [ 'name' => $fieldResultElement['name'] ?? null, 'column' => $fieldResultElement['column'] ?? null, ]; } } $entities[] = $entityResult; } } if (isset($resultSetMapping['columnResult'])) { foreach ($resultSetMapping['columnResult'] as $columnResultAnnot) { $columns[] = [ 'name' => $columnResultAnnot['name'] ?? null, ]; } } $metadata->addSqlResultSetMapping( [ 'name' => $resultSetMapping['name'], 'entities' => $entities, 'columns' => $columns, ] ); } } if (isset($element['inheritanceType'])) { $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType']))); if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { // Evaluate discriminatorColumn if (isset($element['discriminatorColumn'])) { $discrColumn = $element['discriminatorColumn']; $metadata->setDiscriminatorColumn( [ 'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null, 'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string', 'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255, 'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string) $discrColumn['columnDefinition'] : null, 'enumType' => isset($discrColumn['enumType']) ? (string) $discrColumn['enumType'] : null, ] ); } else { $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]); } // Evaluate discriminatorMap if (isset($element['discriminatorMap'])) { $metadata->setDiscriminatorMap($element['discriminatorMap']); } } } // Evaluate changeTrackingPolicy if (isset($element['changeTrackingPolicy'])) { $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . strtoupper($element['changeTrackingPolicy']))); } // Evaluate indexes if (isset($element['indexes'])) { foreach ($element['indexes'] as $name => $indexYml) { if (! isset($indexYml['name'])) { $indexYml['name'] = $name; } $index = []; if (isset($indexYml['columns'])) { if (is_string($indexYml['columns'])) { $index['columns'] = array_map('trim', explode(',', $indexYml['columns'])); } else { $index['columns'] = $indexYml['columns']; } } if (isset($indexYml['fields'])) { if (is_string($indexYml['fields'])) { $index['fields'] = array_map('trim', explode(',', $indexYml['fields'])); } else { $index['fields'] = $indexYml['fields']; } } if ( isset($index['columns'], $index['fields']) || ( ! isset($index['columns']) && ! isset($index['fields']) ) ) { throw MappingException::invalidIndexConfiguration( $className, $indexYml['name'] ); } if (isset($indexYml['flags'])) { if (is_string($indexYml['flags'])) { $index['flags'] = array_map('trim', explode(',', $indexYml['flags'])); } else { $index['flags'] = $indexYml['flags']; } } if (isset($indexYml['options'])) { $index['options'] = $indexYml['options']; } $metadata->table['indexes'][$indexYml['name']] = $index; } } // Evaluate uniqueConstraints if (isset($element['uniqueConstraints'])) { foreach ($element['uniqueConstraints'] as $name => $uniqueYml) { if (! isset($uniqueYml['name'])) { $uniqueYml['name'] = $name; } $unique = []; if (isset($uniqueYml['columns'])) { if (is_string($uniqueYml['columns'])) { $unique['columns'] = array_map('trim', explode(',', $uniqueYml['columns'])); } else { $unique['columns'] = $uniqueYml['columns']; } } if (isset($uniqueYml['fields'])) { if (is_string($uniqueYml['fields'])) { $unique['fields'] = array_map('trim', explode(',', $uniqueYml['fields'])); } else { $unique['fields'] = $uniqueYml['fields']; } } if ( isset($unique['columns'], $unique['fields']) || ( ! isset($unique['columns']) && ! isset($unique['fields']) ) ) { throw MappingException::invalidUniqueConstraintConfiguration( $className, $uniqueYml['name'] ); } if (isset($uniqueYml['options'])) { $unique['options'] = $uniqueYml['options']; } $metadata->table['uniqueConstraints'][$uniqueYml['name']] = $unique; } } if (isset($element['options'])) { $metadata->table['options'] = $element['options']; } $associationIds = []; if (isset($element['id'])) { // Evaluate identifier settings foreach ($element['id'] as $name => $idElement) { if (isset($idElement['associationKey']) && $idElement['associationKey'] === true) { $associationIds[$name] = true; continue; } $mapping = [ 'id' => true, 'fieldName' => $name, ]; if (isset($idElement['type'])) { $mapping['type'] = $idElement['type']; } if (isset($idElement['column'])) { $mapping['columnName'] = $idElement['column']; } if (isset($idElement['length'])) { $mapping['length'] = $idElement['length']; } if (isset($idElement['columnDefinition'])) { $mapping['columnDefinition'] = $idElement['columnDefinition']; } if (isset($idElement['options'])) { $mapping['options'] = $idElement['options']; } $metadata->mapField($mapping); if (isset($idElement['generator'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($idElement['generator']['strategy']))); } // Check for SequenceGenerator definition if (isset($idElement['sequenceGenerator'])) { $metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']); } elseif (isset($idElement['customIdGenerator'])) { $customGenerator = $idElement['customIdGenerator']; $metadata->setCustomGeneratorDefinition( [ 'class' => (string) $customGenerator['class'], ] ); } } } // Evaluate fields if (isset($element['fields'])) { foreach ($element['fields'] as $name => $fieldMapping) { $mapping = $this->columnToArray($name, $fieldMapping); if (isset($fieldMapping['id'])) { $mapping['id'] = true; if (isset($fieldMapping['generator']['strategy'])) { $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($fieldMapping['generator']['strategy']))); } } if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); unset($mapping['version']); } $metadata->mapField($mapping); } } if (isset($element['embedded'])) { foreach ($element['embedded'] as $name => $embeddedMapping) { $mapping = [ 'fieldName' => $name, 'class' => $embeddedMapping['class'] ?? null, 'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null, ]; $metadata->mapEmbedded($mapping); } } // Evaluate oneToOne relationships if (isset($element['oneToOne'])) { foreach ($element['oneToOne'] as $name => $oneToOneElement) { $mapping = [ 'fieldName' => $name, 'targetEntity' => $oneToOneElement['targetEntity'] ?? null, ]; if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($oneToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']); } if (isset($oneToOneElement['mappedBy'])) { $mapping['mappedBy'] = $oneToOneElement['mappedBy']; } else { if (isset($oneToOneElement['inversedBy'])) { $mapping['inversedBy'] = $oneToOneElement['inversedBy']; } $joinColumns = []; if (isset($oneToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($oneToOneElement['joinColumn']); } elseif (isset($oneToOneElement['joinColumns'])) { foreach ($oneToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; } if (isset($oneToOneElement['cascade'])) { $mapping['cascade'] = $oneToOneElement['cascade']; } if (isset($oneToOneElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool) $oneToOneElement['orphanRemoval']; } // Evaluate second level cache if (isset($oneToOneElement['cache'])) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache'])); } $metadata->mapOneToOne($mapping); } } // Evaluate oneToMany relationships if (isset($element['oneToMany'])) { foreach ($element['oneToMany'] as $name => $oneToManyElement) { $mapping = [ 'fieldName' => $name, 'targetEntity' => $oneToManyElement['targetEntity'], 'mappedBy' => $oneToManyElement['mappedBy'], ]; if (isset($oneToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyElement['fetch']); } if (isset($oneToManyElement['cascade'])) { $mapping['cascade'] = $oneToManyElement['cascade']; } if (isset($oneToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool) $oneToManyElement['orphanRemoval']; } if (isset($oneToManyElement['orderBy'])) { $mapping['orderBy'] = $oneToManyElement['orderBy']; } if (isset($oneToManyElement['indexBy'])) { $mapping['indexBy'] = $oneToManyElement['indexBy']; } // Evaluate second level cache if (isset($oneToManyElement['cache'])) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache'])); } $metadata->mapOneToMany($mapping); } } // Evaluate manyToOne relationships if (isset($element['manyToOne'])) { foreach ($element['manyToOne'] as $name => $manyToOneElement) { $mapping = [ 'fieldName' => $name, 'targetEntity' => $manyToOneElement['targetEntity'] ?? null, ]; if (isset($associationIds[$mapping['fieldName']])) { $mapping['id'] = true; } if (isset($manyToOneElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']); } if (isset($manyToOneElement['inversedBy'])) { $mapping['inversedBy'] = $manyToOneElement['inversedBy']; } $joinColumns = []; if (isset($manyToOneElement['joinColumn'])) { $joinColumns[] = $this->joinColumnToArray($manyToOneElement['joinColumn']); } elseif (isset($manyToOneElement['joinColumns'])) { foreach ($manyToOneElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinColumns'] = $joinColumns; if (isset($manyToOneElement['cascade'])) { $mapping['cascade'] = $manyToOneElement['cascade']; } // Evaluate second level cache if (isset($manyToOneElement['cache'])) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache'])); } $metadata->mapManyToOne($mapping); } } // Evaluate manyToMany relationships if (isset($element['manyToMany'])) { foreach ($element['manyToMany'] as $name => $manyToManyElement) { $mapping = [ 'fieldName' => $name, 'targetEntity' => $manyToManyElement['targetEntity'], ]; if (isset($manyToManyElement['fetch'])) { $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyElement['fetch']); } if (isset($manyToManyElement['mappedBy'])) { $mapping['mappedBy'] = $manyToManyElement['mappedBy']; } elseif (isset($manyToManyElement['joinTable'])) { $joinTableElement = $manyToManyElement['joinTable']; $joinTable = [ 'name' => $joinTableElement['name'], ]; if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } if (isset($joinTableElement['joinColumns'])) { foreach ($joinTableElement['joinColumns'] as $joinColumnName => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } if (isset($joinTableElement['inverseJoinColumns'])) { foreach ($joinTableElement['inverseJoinColumns'] as $joinColumnName => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $joinColumnName; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } } $mapping['joinTable'] = $joinTable; } if (isset($manyToManyElement['inversedBy'])) { $mapping['inversedBy'] = $manyToManyElement['inversedBy']; } if (isset($manyToManyElement['cascade'])) { $mapping['cascade'] = $manyToManyElement['cascade']; } if (isset($manyToManyElement['orderBy'])) { $mapping['orderBy'] = $manyToManyElement['orderBy']; } if (isset($manyToManyElement['indexBy'])) { $mapping['indexBy'] = $manyToManyElement['indexBy']; } if (isset($manyToManyElement['orphanRemoval'])) { $mapping['orphanRemoval'] = (bool) $manyToManyElement['orphanRemoval']; } // Evaluate second level cache if (isset($manyToManyElement['cache'])) { $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache'])); } $metadata->mapManyToMany($mapping); } } // Evaluate associationOverride if (isset($element['associationOverride']) && is_array($element['associationOverride'])) { foreach ($element['associationOverride'] as $fieldName => $associationOverrideElement) { $override = []; // Check for joinColumn if (isset($associationOverrideElement['joinColumn'])) { $joinColumns = []; foreach ($associationOverrideElement['joinColumn'] as $name => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinColumns[] = $this->joinColumnToArray($joinColumnElement); } $override['joinColumns'] = $joinColumns; } // Check for joinTable if (isset($associationOverrideElement['joinTable'])) { $joinTableElement = $associationOverrideElement['joinTable']; $joinTable = [ 'name' => $joinTableElement['name'], ]; if (isset($joinTableElement['schema'])) { $joinTable['schema'] = $joinTableElement['schema']; } foreach ($joinTableElement['joinColumns'] as $name => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement); } foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) { if (! isset($joinColumnElement['name'])) { $joinColumnElement['name'] = $name; } $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement); } $override['joinTable'] = $joinTable; } // Check for inversedBy if (isset($associationOverrideElement['inversedBy'])) { $override['inversedBy'] = (string) $associationOverrideElement['inversedBy']; } // Check for `fetch` if (isset($associationOverrideElement['fetch'])) { $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverrideElement['fetch']); } $metadata->setAssociationOverride($fieldName, $override); } } // Evaluate associationOverride if (isset($element['attributeOverride']) && is_array($element['attributeOverride'])) { foreach ($element['attributeOverride'] as $fieldName => $attributeOverrideElement) { $mapping = $this->columnToArray($fieldName, $attributeOverrideElement); $metadata->setAttributeOverride($fieldName, $mapping); } } // Evaluate lifeCycleCallbacks if (isset($element['lifecycleCallbacks'])) { foreach ($element['lifecycleCallbacks'] as $type => $methods) { foreach ($methods as $method) { $metadata->addLifecycleCallback($method, constant('Doctrine\ORM\Events::' . $type)); } } } // Evaluate entityListeners if (isset($element['entityListeners'])) { foreach ($element['entityListeners'] as $className => $entityListener) { // Evaluate the listener using naming convention. if (empty($entityListener)) { EntityListenerBuilder::bindEntityListener($metadata, $className); continue; } foreach ($entityListener as $eventName => $callbackElement) { foreach ($callbackElement as $methodName) { $metadata->addEntityListener($eventName, $className, $methodName); } } } } } /** * Constructs a joinColumn mapping array based on the information * found in the given join column element. * * @psalm-param array{ * referencedColumnName?: mixed, * name?: mixed, * fieldName?: mixed, * unique?: mixed, * nullable?: mixed, * onDelete?: mixed, * columnDefinition?: mixed * } $joinColumnElement The array join column element. * * @return mixed[] The mapping array. * @psalm-return array{ * referencedColumnName?: string, * name?: string, * fieldName?: string, * unique?: bool, * nullable?: bool, * onDelete?: mixed, * columnDefinition?: mixed * } */ private function joinColumnToArray(array $joinColumnElement): array { $joinColumn = []; if (isset($joinColumnElement['referencedColumnName'])) { $joinColumn['referencedColumnName'] = (string) $joinColumnElement['referencedColumnName']; } if (isset($joinColumnElement['name'])) { $joinColumn['name'] = (string) $joinColumnElement['name']; } if (isset($joinColumnElement['fieldName'])) { $joinColumn['fieldName'] = (string) $joinColumnElement['fieldName']; } if (isset($joinColumnElement['unique'])) { $joinColumn['unique'] = (bool) $joinColumnElement['unique']; } if (isset($joinColumnElement['nullable'])) { $joinColumn['nullable'] = (bool) $joinColumnElement['nullable']; } if (isset($joinColumnElement['onDelete'])) { $joinColumn['onDelete'] = $joinColumnElement['onDelete']; } if (isset($joinColumnElement['columnDefinition'])) { $joinColumn['columnDefinition'] = $joinColumnElement['columnDefinition']; } return $joinColumn; } /** * Parses the given column as array. * * @psalm-param array{ * type?: string, * column?: string, * precision?: mixed, * scale?: mixed, * unique?: mixed, * options?: mixed, * nullable?: mixed, * insertable?: mixed, * updatable?: mixed, * generated?: mixed, * enumType?: class-string, * version?: mixed, * columnDefinition?: mixed * }|null $column * * @return mixed[] * @psalm-return array{ * fieldName: string, * type?: string, * columnName?: string, * length?: int, * precision?: mixed, * scale?: mixed, * unique?: bool, * options?: mixed, * nullable?: mixed, * notInsertable?: mixed, * notUpdatable?: mixed, * generated?: mixed, * enumType?: class-string, * version?: mixed, * columnDefinition?: mixed * } */ private function columnToArray(string $fieldName, ?array $column): array { $mapping = ['fieldName' => $fieldName]; if (isset($column['type'])) { $params = explode('(', $column['type']); $column['type'] = $params[0]; $mapping['type'] = $column['type']; if (isset($params[1])) { $column['length'] = (int) substr($params[1], 0, strlen($params[1]) - 1); } } if (isset($column['column'])) { $mapping['columnName'] = $column['column']; } if (isset($column['length'])) { $mapping['length'] = $column['length']; } if (isset($column['precision'])) { $mapping['precision'] = $column['precision']; } if (isset($column['scale'])) { $mapping['scale'] = $column['scale']; } if (isset($column['unique'])) { $mapping['unique'] = (bool) $column['unique']; } if (isset($column['options'])) { $mapping['options'] = $column['options']; } if (isset($column['nullable'])) { $mapping['nullable'] = $column['nullable']; } if (isset($column['insertable']) && ! (bool) $column['insertable']) { $mapping['notInsertable'] = true; } if (isset($column['updatable']) && ! (bool) $column['updatable']) { $mapping['notUpdatable'] = true; } if (isset($column['generated'])) { $mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $column['generated']); } if (isset($column['version']) && $column['version']) { $mapping['version'] = $column['version']; } if (isset($column['columnDefinition'])) { $mapping['columnDefinition'] = $column['columnDefinition']; } if (isset($column['enumType'])) { $mapping['enumType'] = $column['enumType']; } return $mapping; } /** * Parse / Normalize the cache configuration * * @param mixed[] $cacheMapping * @psalm-param array{usage: string|null, region?: mixed} $cacheMapping * * @return mixed[] * @psalm-return array{usage: int|null, region: string|null} */ private function cacheToArray(array $cacheMapping): array { $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null; $usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null; if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) { throw new InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage)); } if ($usage) { $usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage); } return [ 'usage' => $usage, 'region' => $region, ]; } /** * {@inheritDoc} */ protected function loadMappingFile($file) { return Yaml::parse(file_get_contents($file)); } } orm/lib/Doctrine/ORM/Mapping/Exception/CannotGenerateIds.php 0000644 00000000773 15120025734 0017720 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Exception\ORMException; use function get_debug_type; use function sprintf; final class CannotGenerateIds extends ORMException { public static function withPlatform(AbstractPlatform $platform): self { return new self(sprintf( 'Platform %s does not support generating identifiers', get_debug_type($platform) )); } } orm/lib/Doctrine/ORM/Mapping/Exception/InvalidCustomGenerator.php 0000644 00000001206 15120025734 0021003 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Exception; use Doctrine\ORM\Exception\ORMException; use function sprintf; use function var_export; final class InvalidCustomGenerator extends ORMException { public static function onClassNotConfigured(): self { return new self('Cannot instantiate custom generator, no class has been defined'); } /** @param mixed[] $definition */ public static function onMissingClass(array $definition): self { return new self(sprintf( 'Cannot instantiate custom generator : %s', var_export($definition, true) )); } } orm/lib/Doctrine/ORM/Mapping/Exception/UnknownGeneratorType.php 0000644 00000000476 15120025734 0020533 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Exception; use Doctrine\ORM\Exception\ORMException; final class UnknownGeneratorType extends ORMException { public static function create(int $generatorType): self { return new self('Unknown generator type: ' . $generatorType); } } orm/lib/Doctrine/ORM/Mapping/JoinColumn.php 0000644 00000000614 15120025734 0014474 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target({"PROPERTY","ANNOTATION"}) */ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] final class JoinColumn implements MappingAttribute { use JoinColumnProperties; } orm/lib/Doctrine/ORM/Mapping/JoinColumnProperties.php 0000644 00000003171 15120025734 0016552 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; trait JoinColumnProperties { /** * @var string|null * @readonly */ public $name; /** * @var string * @readonly */ public $referencedColumnName = 'id'; /** * @var bool * @readonly */ public $unique = false; /** * @var bool * @readonly */ public $nullable = true; /** * @var mixed * @readonly */ public $onDelete; /** * @var string|null * @readonly */ public $columnDefinition; /** * Field name used in non-object hydration (array/scalar). * * @var string|null * @readonly */ public $fieldName; /** * @var array<string, mixed> * @readonly */ public $options = []; /** * @param mixed $onDelete * @param array<string, mixed> $options */ public function __construct( ?string $name = null, string $referencedColumnName = 'id', bool $unique = false, bool $nullable = true, $onDelete = null, ?string $columnDefinition = null, ?string $fieldName = null, array $options = [] ) { $this->name = $name; $this->referencedColumnName = $referencedColumnName; $this->unique = $unique; $this->nullable = $nullable; $this->onDelete = $onDelete; $this->columnDefinition = $columnDefinition; $this->fieldName = $fieldName; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/JoinColumns.php 0000644 00000000363 15120025734 0014660 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("PROPERTY") */ final class JoinColumns implements MappingAttribute { /** @var array<\Doctrine\ORM\Mapping\JoinColumn> */ public $value; } orm/lib/Doctrine/ORM/Mapping/JoinTable.php 0000644 00000002605 15120025734 0014270 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target({"PROPERTY","ANNOTATION"}) */ #[Attribute(Attribute::TARGET_PROPERTY)] final class JoinTable implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var string|null * @readonly */ public $schema; /** * @var array<JoinColumn> * @readonly */ public $joinColumns = []; /** * @var array<JoinColumn> * @readonly */ public $inverseJoinColumns = []; /** * @var array<string, mixed> * @readonly */ public $options = []; /** @param array<string, mixed> $options */ public function __construct( ?string $name = null, ?string $schema = null, $joinColumns = [], $inverseJoinColumns = [], array $options = [] ) { $this->name = $name; $this->schema = $schema; $this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns; $this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn ? [$inverseJoinColumns] : $inverseJoinColumns; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/ManyToMany.php 0000644 00000004057 15120025734 0014460 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\Deprecations\Deprecation; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class ManyToMany implements MappingAttribute { /** * @var class-string|null * @readonly */ public $targetEntity; /** * @var string|null * @readonly */ public $mappedBy; /** * @var string|null * @readonly */ public $inversedBy; /** * @var string[]|null * @readonly */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY' * @readonly * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var bool * @readonly */ public $orphanRemoval = false; /** * @var string|null * @readonly */ public $indexBy; /** * @param class-string|null $targetEntity * @param string[]|null $cascade * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch */ public function __construct( ?string $targetEntity = null, ?string $mappedBy = null, ?string $inversedBy = null, ?array $cascade = null, string $fetch = 'LAZY', bool $orphanRemoval = false, ?string $indexBy = null ) { if ($targetEntity === null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8753', 'Passing no target entity is deprecated.' ); } $this->targetEntity = $targetEntity; $this->mappedBy = $mappedBy; $this->inversedBy = $inversedBy; $this->cascade = $cascade; $this->fetch = $fetch; $this->orphanRemoval = $orphanRemoval; $this->indexBy = $indexBy; } } orm/lib/Doctrine/ORM/Mapping/ManyToOne.php 0000644 00000002451 15120025734 0014271 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class ManyToOne implements MappingAttribute { /** * @var class-string|null * @readonly */ public $targetEntity; /** * @var string[]|null * @readonly */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY' * @readonly * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var string|null * @readonly */ public $inversedBy; /** * @param class-string|null $targetEntity * @param string[]|null $cascade * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch */ public function __construct( ?string $targetEntity = null, ?array $cascade = null, string $fetch = 'LAZY', ?string $inversedBy = null ) { $this->targetEntity = $targetEntity; $this->cascade = $cascade; $this->fetch = $fetch; $this->inversedBy = $inversedBy; } } orm/lib/Doctrine/ORM/Mapping/MappedSuperclass.php 0000644 00000001301 15120025734 0015664 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\ORM\EntityRepository; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class MappedSuperclass implements MappingAttribute { /** * @var string|null * @psalm-var class-string<EntityRepository>|null * @readonly */ public $repositoryClass; /** @psalm-param class-string<EntityRepository>|null $repositoryClass */ public function __construct(?string $repositoryClass = null) { $this->repositoryClass = $repositoryClass; } } orm/lib/Doctrine/ORM/Mapping/MappingAttribute.php 0000644 00000000246 15120025734 0015677 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** A marker interface for mapping attributes. */ interface MappingAttribute extends Annotation { } orm/lib/Doctrine/ORM/Mapping/MappingException.php 0000644 00000071627 15120025734 0015705 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use BackedEnum; use Doctrine\ORM\Exception\ORMException; use LibXMLError; use ReflectionException; use ValueError; use function array_keys; use function array_map; use function array_values; use function get_debug_type; use function get_parent_class; use function implode; use function sprintf; use const PHP_EOL; /** * A MappingException indicates that something is wrong with the mapping setup. */ class MappingException extends ORMException { /** @return MappingException */ public static function pathRequired() { return new self('Specifying the paths to your entities is required ' . 'in the AnnotationDriver to retrieve all class names.'); } /** * @param class-string $entityName * * @return MappingException */ public static function identifierRequired($entityName) { $parent = get_parent_class($entityName); if ($parent !== false) { return new self(sprintf( 'No identifier/primary key specified for Entity "%s" sub class of "%s". Every Entity must have an identifier/primary key.', $entityName, $parent )); } return new self(sprintf( 'No identifier/primary key specified for Entity "%s". Every Entity must have an identifier/primary key.', $entityName )); } /** * @param string $entityName * @param int $type * * @return MappingException */ public static function invalidInheritanceType($entityName, $type) { return new self(sprintf("The inheritance type '%s' specified for '%s' does not exist.", $type, $entityName)); } /** @return MappingException */ public static function generatorNotAllowedWithCompositeId() { return new self("Id generators can't be used with a composite id."); } /** * @param string $entity * * @return MappingException */ public static function missingFieldName($entity) { return new self(sprintf( "The field or association mapping misses the 'fieldName' attribute in entity '%s'.", $entity )); } /** * @param string $fieldName * * @return MappingException */ public static function missingTargetEntity($fieldName) { return new self(sprintf("The association mapping '%s' misses the 'targetEntity' attribute.", $fieldName)); } /** * @param string $fieldName * * @return MappingException */ public static function missingSourceEntity($fieldName) { return new self(sprintf("The association mapping '%s' misses the 'sourceEntity' attribute.", $fieldName)); } /** * @param string $fieldName * * @return MappingException */ public static function missingEmbeddedClass($fieldName) { return new self(sprintf("The embed mapping '%s' misses the 'class' attribute.", $fieldName)); } /** * @param string $entityName * @param string $fileName * * @return MappingException */ public static function mappingFileNotFound($entityName, $fileName) { return new self(sprintf("No mapping file found named '%s' for class '%s'.", $fileName, $entityName)); } /** * Exception for invalid property name override. * * @param string $className The entity's name. * @param string $fieldName * * @return MappingException */ public static function invalidOverrideFieldName($className, $fieldName) { return new self(sprintf("Invalid field override named '%s' for class '%s'.", $fieldName, $className)); } /** * Exception for invalid property type override. * * @param string $className The entity's name. * @param string $fieldName * * @return MappingException */ public static function invalidOverrideFieldType($className, $fieldName) { return new self(sprintf( "The column type of attribute '%s' on class '%s' could not be changed.", $fieldName, $className )); } /** * @param string $className * @param string $fieldName * * @return MappingException */ public static function mappingNotFound($className, $fieldName) { return new self(sprintf("No mapping found for field '%s' on class '%s'.", $fieldName, $className)); } /** * @param string $className * @param string $queryName * * @return MappingException */ public static function queryNotFound($className, $queryName) { return new self(sprintf("No query found named '%s' on class '%s'.", $queryName, $className)); } /** * @param string $className * @param string $resultName * * @return MappingException */ public static function resultMappingNotFound($className, $resultName) { return new self(sprintf("No result set mapping found named '%s' on class '%s'.", $resultName, $className)); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function emptyQueryMapping($entity, $queryName) { return new self(sprintf('Query named "%s" in "%s" could not be empty.', $queryName, $entity)); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForQueryMapping($className) { return new self(sprintf("Query name on entity class '%s' is not defined.", $className)); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function missingQueryMapping($entity, $queryName) { return new self(sprintf( 'Query named "%s" in "%s requires a result class or result set mapping.', $queryName, $entity )); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function missingResultSetMappingEntity($entity, $resultName) { return new self(sprintf( 'Result set mapping named "%s" in "%s requires a entity class name.', $resultName, $entity )); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function missingResultSetMappingFieldName($entity, $resultName) { return new self(sprintf( 'Result set mapping named "%s" in "%s requires a field name.', $resultName, $entity )); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForSqlResultSetMapping($className) { return new self(sprintf("Result set mapping name on entity class '%s' is not defined.", $className)); } public static function oneToManyRequiresMappedBy(string $entityName, string $fieldName): MappingException { return new self(sprintf( "OneToMany mapping on entity '%s' field '%s' requires the 'mappedBy' attribute.", $entityName, $fieldName )); } /** * @param string $fieldName * * @return MappingException */ public static function joinTableRequired($fieldName) { return new self(sprintf("The mapping of field '%s' requires an the 'joinTable' attribute.", $fieldName)); } /** * Called if a required option was not found but is required * * @param string $field Which field cannot be processed? * @param string $expectedOption Which option is required * @param string $hint Can optionally be used to supply a tip for common mistakes, * e.g. "Did you think of the plural s?" * * @return MappingException */ public static function missingRequiredOption($field, $expectedOption, $hint = '') { $message = "The mapping of field '" . $field . "' is invalid: The option '" . $expectedOption . "' is required."; if (! empty($hint)) { $message .= ' (Hint: ' . $hint . ')'; } return new self($message); } /** * Generic exception for invalid mappings. * * @param string $fieldName * * @return MappingException */ public static function invalidMapping($fieldName) { return new self(sprintf("The mapping of field '%s' is invalid.", $fieldName)); } /** * Exception for reflection exceptions - adds the entity name, * because there might be long classnames that will be shortened * within the stacktrace * * @param string $entity The entity's name * * @return MappingException */ public static function reflectionFailure($entity, ReflectionException $previousException) { return new self(sprintf('An error occurred in %s', $entity), 0, $previousException); } /** * @param string $className * @param string $joinColumn * * @return MappingException */ public static function joinColumnMustPointToMappedField($className, $joinColumn) { return new self(sprintf( 'The column %s must be mapped to a field in class %s since it is referenced by a join column of another class.', $joinColumn, $className )); } /** * @param class-string $className * * @return MappingException */ public static function classIsNotAValidEntityOrMappedSuperClass($className) { $parent = get_parent_class($className); if ($parent !== false) { return new self(sprintf( 'Class "%s" sub class of "%s" is not a valid entity or mapped super class.', $className, $parent )); } return new self(sprintf( 'Class "%s" is not a valid entity or mapped super class.', $className )); } /** * @deprecated 2.9 no longer in use * * @param string $className * @param string $propertyName * * @return MappingException */ public static function propertyTypeIsRequired($className, $propertyName) { return new self(sprintf( "The attribute 'type' is required for the column description of property %s::\$%s.", $className, $propertyName )); } /** * @param string $entity The entity's name. * @param string $fieldName The name of the field that was already declared. * * @return MappingException */ public static function duplicateFieldMapping($entity, $fieldName) { return new self(sprintf( 'Property "%s" in "%s" was already declared, but it must be declared only once', $fieldName, $entity )); } /** * @param string $entity * @param string $fieldName * * @return MappingException */ public static function duplicateAssociationMapping($entity, $fieldName) { return new self(sprintf( 'Property "%s" in "%s" was already declared, but it must be declared only once', $fieldName, $entity )); } /** * @param string $entity * @param string $queryName * * @return MappingException */ public static function duplicateQueryMapping($entity, $queryName) { return new self(sprintf( 'Query named "%s" in "%s" was already declared, but it must be declared only once', $queryName, $entity )); } /** * @param string $entity * @param string $resultName * * @return MappingException */ public static function duplicateResultSetMapping($entity, $resultName) { return new self(sprintf( 'Result set mapping named "%s" in "%s" was already declared, but it must be declared only once', $resultName, $entity )); } /** * @param string $entity * * @return MappingException */ public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { return new self('Single id is not allowed on composite primary key in entity ' . $entity); } /** * @param string $entity * * @return MappingException */ public static function noIdDefined($entity) { return new self('No ID defined for entity ' . $entity); } /** * @param string $entity * @param string $fieldName * @param string $unsupportedType * * @return MappingException */ public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) { return new self(sprintf( 'Locking type "%s" (specified in "%s", field "%s") is not supported by Doctrine.', $unsupportedType, $entity, $fieldName )); } /** * @param string|null $path * * @return MappingException */ public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null) { if (! empty($path)) { $path = '[' . $path . ']'; } return new self( 'File mapping drivers must have a valid directory path, ' . 'however the given path ' . $path . ' seems to be incorrect!' ); } /** * Returns an exception that indicates that a class used in a discriminator map does not exist. * An example would be an outdated (maybe renamed) classname. * * @param string $className The class that could not be found * @param string $owningClass The class that declares the discriminator map. * * @return MappingException */ public static function invalidClassInDiscriminatorMap($className, $owningClass) { return new self(sprintf( "Entity class '%s' used in the discriminator map of class '%s' " . 'does not exist.', $className, $owningClass )); } /** * @param string $className * @param string[] $entries * @param array<string,string> $map * * @return MappingException */ public static function duplicateDiscriminatorEntry($className, array $entries, array $map) { return new self( 'The entries ' . implode(', ', $entries) . " in discriminator map of class '" . $className . "' is duplicated. " . 'If the discriminator map is automatically generated you have to convert it to an explicit discriminator map now. ' . 'The entries of the current map are: @DiscriminatorMap({' . implode(', ', array_map( static function ($a, $b) { return sprintf("'%s': '%s'", $a, $b); }, array_keys($map), array_values($map) )) . '})' ); } /** * @param string $className * * @return MappingException */ public static function missingDiscriminatorMap($className) { return new self(sprintf( "Entity class '%s' is using inheritance but no discriminator map was defined.", $className )); } /** * @param string $className * * @return MappingException */ public static function missingDiscriminatorColumn($className) { return new self(sprintf( "Entity class '%s' is using inheritance but no discriminator column was defined.", $className )); } /** * @param string $className * @param string $type * * @return MappingException */ public static function invalidDiscriminatorColumnType($className, $type) { return new self(sprintf( "Discriminator column type on entity class '%s' is not allowed to be '%s'. 'string' or 'integer' type variables are suggested!", $className, $type )); } /** * @param string $className * * @return MappingException */ public static function nameIsMandatoryForDiscriminatorColumns($className) { return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className)); } /** * @param string $className * @param string $fieldName * * @return MappingException */ public static function cannotVersionIdField($className, $fieldName) { return new self(sprintf( "Setting Id field '%s' as versionable in entity class '%s' is not supported.", $fieldName, $className )); } /** * @param string $className * @param string $fieldName * @param string $type * * @return MappingException */ public static function sqlConversionNotAllowedForIdentifiers($className, $fieldName, $type) { return new self(sprintf( "It is not possible to set id field '%s' to type '%s' in entity class '%s'. The type '%s' requires conversion SQL which is not allowed for identifiers.", $fieldName, $type, $className, $type )); } /** * @param string $className * @param string $columnName * * @return MappingException */ public static function duplicateColumnName($className, $columnName) { return new self("Duplicate definition of column '" . $columnName . "' on entity '" . $className . "' in a field or discriminator column mapping."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalToManyAssociationOnMappedSuperclass($className, $field) { return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '" . $className . '#' . $field . "'."); } /** * @param string $className * @param string $targetEntity * @param string $targetField * * @return MappingException */ public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField) { return new self("It is not possible to map entity '" . $className . "' with a composite primary key " . "as part of the primary key of another entity '" . $targetEntity . '#' . $targetField . "'."); } /** * @param string $className * @param string $field * * @return MappingException */ public static function noSingleAssociationJoinColumnFound($className, $field) { return new self(sprintf("'%s#%s' is not an association with a single join column.", $className, $field)); } /** * @param string $className * @param string $column * * @return MappingException */ public static function noFieldNameFoundForColumn($className, $column) { return new self(sprintf( "Cannot find a field on '%s' that is mapped to column '%s'. Either the " . 'field does not exist or an association exists but it has multiple join columns.', $className, $column )); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field) { return new self(sprintf( "The orphan removal option is not allowed on an association that is part of the identifier in '%s#%s'.", $className, $field )); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalOrphanRemoval($className, $field) { return new self('Orphan removal is only allowed on one-to-one and one-to-many ' . 'associations, but ' . $className . '#' . $field . ' is not.'); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalInverseIdentifierAssociation($className, $field) { return new self(sprintf( "An inverse association is not allowed to be identifier in '%s#%s'.", $className, $field )); } /** * @param string $className * @param string $field * * @return MappingException */ public static function illegalToManyIdentifierAssociation($className, $field) { return new self(sprintf( "Many-to-many or one-to-many associations are not allowed to be identifier in '%s#%s'.", $className, $field )); } /** * @param string $className * * @return MappingException */ public static function noInheritanceOnMappedSuperClass($className) { return new self("It is not supported to define inheritance information on a mapped superclass '" . $className . "'."); } /** * @param string $className * @param string $rootClassName * * @return MappingException */ public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName) { return new self( "Entity '" . $className . "' has to be part of the discriminator map of '" . $rootClassName . "' " . "to be properly mapped in the inheritance hierarchy. Alternatively you can make '" . $className . "' an abstract class " . 'to avoid this exception from occurring.' ); } /** * @param string $className * @param string $methodName * * @return MappingException */ public static function lifecycleCallbackMethodNotFound($className, $methodName) { return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback."); } /** * @param string $listenerName * @param string $className * * @return MappingException */ public static function entityListenerClassNotFound($listenerName, $className) { return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className)); } /** * @param string $listenerName * @param string $methodName * @param string $className * * @return MappingException */ public static function entityListenerMethodNotFound($listenerName, $methodName, $className) { return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); } /** * @param string $listenerName * @param string $methodName * @param string $className * * @return MappingException */ public static function duplicateEntityListener($listenerName, $methodName, $className) { return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className)); } /** @param class-string $className */ public static function invalidFetchMode(string $className, string $fetchMode): self { return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $fetchMode . "'"); } /** @param int|string $generatedMode */ public static function invalidGeneratedMode($generatedMode): self { return new self("Invalid generated mode '" . $generatedMode . "'"); } /** * @param string $className * * @return MappingException */ public static function compositeKeyAssignedIdGeneratorRequired($className) { return new self("Entity '" . $className . "' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported."); } /** * @param string $targetEntity * @param string $sourceEntity * @param string $associationName * * @return MappingException */ public static function invalidTargetEntityClass($targetEntity, $sourceEntity, $associationName) { return new self('The target-entity ' . $targetEntity . " cannot be found in '" . $sourceEntity . '#' . $associationName . "'."); } /** * @param string[] $cascades * @param string $className * @param string $propertyName * * @return MappingException */ public static function invalidCascadeOption(array $cascades, $className, $propertyName) { $cascades = implode(', ', array_map(static function ($e) { return "'" . $e . "'"; }, $cascades)); return new self(sprintf( "You have specified invalid cascade options for %s::$%s: %s; available options: 'remove', 'persist', 'refresh', 'merge', and 'detach'", $className, $propertyName, $cascades )); } /** * @param string $className * * @return MappingException */ public static function missingSequenceName($className) { return new self( sprintf('Missing "sequenceName" attribute for sequence id generator definition on class "%s".', $className) ); } /** * @param string $className * @param string $propertyName * * @return MappingException */ public static function infiniteEmbeddableNesting($className, $propertyName) { return new self( sprintf( 'Infinite nesting detected for embedded property %s::%s. ' . 'You cannot embed an embeddable from the same type inside an embeddable.', $className, $propertyName ) ); } /** * @param string $className * @param string $propertyName * * @return self */ public static function illegalOverrideOfInheritedProperty($className, $propertyName) { return new self( sprintf( 'Overrides are only allowed for fields or associations declared in mapped superclasses or traits, which is not the case for %s::%s.', $className, $propertyName ) ); } /** @return self */ public static function invalidIndexConfiguration($className, $indexName) { return new self( sprintf( 'Index %s for entity %s should contain columns or fields values, but not both.', $indexName, $className ) ); } /** @return self */ public static function invalidUniqueConstraintConfiguration($className, $indexName) { return new self( sprintf( 'Unique constraint %s for entity %s should contain columns or fields values, but not both.', $indexName, $className ) ); } /** @param mixed $givenValue */ public static function invalidOverrideType(string $expectdType, $givenValue): self { return new self(sprintf( 'Expected %s, but %s was given.', $expectdType, get_debug_type($givenValue) )); } public static function enumsRequirePhp81(string $className, string $fieldName): self { return new self(sprintf('Enum types require PHP 8.1 in %s::$%s', $className, $fieldName)); } public static function nonEnumTypeMapped(string $className, string $fieldName, string $enumType): self { return new self(sprintf( 'Attempting to map non-enum type %s as enum in entity %s::$%s', $enumType, $className, $fieldName )); } /** * @param class-string $className * @param class-string<BackedEnum> $enumType */ public static function invalidEnumValue( string $className, string $fieldName, string $value, string $enumType, ValueError $previous ): self { return new self(sprintf( <<<'EXCEPTION' Context: Trying to hydrate enum property "%s::$%s" Problem: Case "%s" is not listed in enum "%s" Solution: Either add the case to the enum type or migrate the database column to use another case of the enum EXCEPTION , $className, $fieldName, $value, $enumType ), 0, $previous); } /** @param LibXMLError[] $errors */ public static function fromLibXmlErrors(array $errors): self { $formatter = static function (LibXMLError $error): string { return sprintf( 'libxml error: %s in %s at line %d', $error->message, $error->file, $error->line ); }; return new self(implode(PHP_EOL, array_map($formatter, $errors))); } } orm/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php 0000644 00000000734 15120025734 0016153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of native SQL named queries. * The NamedNativeQueries annotation can be applied to an entity or mapped superclass. * * @Annotation * @Target("CLASS") */ final class NamedNativeQueries implements MappingAttribute { /** * One or more NamedNativeQuery annotations. * * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ public $value = []; } orm/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php 0000644 00000001442 15120025734 0015640 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * Is used to specify a native SQL named query. * The NamedNativeQuery annotation can be applied to an entity or mapped superclass. * * @Annotation * @Target("ANNOTATION") */ final class NamedNativeQuery implements MappingAttribute { /** * The name used to refer to the query with the EntityManager methods that create query objects. * * @var string */ public $name; /** * The SQL query string. * * @var string */ public $query; /** * The class of the result. * * @var string */ public $resultClass; /** * The name of a SqlResultSetMapping, as defined in metadata. * * @var string */ public $resultSetMapping; } orm/lib/Doctrine/ORM/Mapping/NamedQueries.php 0000644 00000000361 15120025734 0015000 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("CLASS") */ final class NamedQueries implements MappingAttribute { /** @var array<\Doctrine\ORM\Mapping\NamedQuery> */ public $value; } orm/lib/Doctrine/ORM/Mapping/NamedQuery.php 0000644 00000000375 15120025734 0014475 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * @Annotation * @Target("ANNOTATION") */ final class NamedQuery implements MappingAttribute { /** @var string */ public $name; /** @var string */ public $query; } orm/lib/Doctrine/ORM/Mapping/NamingStrategy.php 0000644 00000004754 15120025734 0015364 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * A set of rules for determining the physical column and table names * * @link www.doctrine-project.org */ interface NamingStrategy { /** * Returns a table name for an entity class. * * @param class-string $className * * @return string A table name. */ public function classToTableName($className); /** * Returns a column name for a property. * * @param string $propertyName A property name. * @param class-string $className The fully-qualified class name. * * @return string A column name. */ public function propertyToColumnName($propertyName, $className = null); /** * Returns a column name for an embedded property. * * @param string $propertyName * @param string $embeddedColumnName * @param class-string $className * @param class-string $embeddedClassName * * @return string */ public function embeddedFieldToColumnName( $propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null ); /** * Returns the default reference column name. * * @return string A column name. */ public function referenceColumnName(); /** * Returns a join column name for a property. * * @param string $propertyName A property name. * * @return string A join column name. */ public function joinColumnName($propertyName/*, string $className */); /** * Returns a join table name. * * @param class-string $sourceEntity The source entity. * @param class-string $targetEntity The target entity. * @param string $propertyName A property name. * * @return string A join table name. */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null); /** * Returns the foreign key column name for the given parameters. * * @param class-string $entityName An entity. * @param string|null $referencedColumnName A property name or null in * case of a self-referencing * entity with join columns * defined in the mapping * * @return string A join column name. */ public function joinKeyColumnName($entityName, $referencedColumnName = null); } orm/lib/Doctrine/ORM/Mapping/OneToMany.php 0000644 00000003157 15120025734 0014275 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class OneToMany implements MappingAttribute { /** * @var string|null * @readonly */ public $mappedBy; /** * @var class-string|null * @readonly */ public $targetEntity; /** * @var array<string>|null * @readonly */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY' * @readonly * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var bool * @readonly */ public $orphanRemoval = false; /** * @var string|null * @readonly */ public $indexBy; /** * @param class-string|null $targetEntity * @param string[]|null $cascade * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch */ public function __construct( ?string $mappedBy = null, ?string $targetEntity = null, ?array $cascade = null, string $fetch = 'LAZY', bool $orphanRemoval = false, ?string $indexBy = null ) { $this->mappedBy = $mappedBy; $this->targetEntity = $targetEntity; $this->cascade = $cascade; $this->fetch = $fetch; $this->orphanRemoval = $orphanRemoval; $this->indexBy = $indexBy; } } orm/lib/Doctrine/ORM/Mapping/OneToOne.php 0000644 00000003171 15120025734 0014106 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class OneToOne implements MappingAttribute { /** * @var class-string|null * @readonly */ public $targetEntity; /** * @var string|null * @readonly */ public $mappedBy; /** * @var string|null * @readonly */ public $inversedBy; /** * @var array<string>|null * @readonly */ public $cascade; /** * The fetching strategy to use for the association. * * @var string * @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY' * @readonly * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch = 'LAZY'; /** * @var bool * @readonly */ public $orphanRemoval = false; /** * @param class-string|null $targetEntity * @param array<string>|null $cascade * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY' $fetch */ public function __construct( ?string $mappedBy = null, ?string $inversedBy = null, ?string $targetEntity = null, ?array $cascade = null, string $fetch = 'LAZY', bool $orphanRemoval = false ) { $this->mappedBy = $mappedBy; $this->inversedBy = $inversedBy; $this->targetEntity = $targetEntity; $this->cascade = $cascade; $this->fetch = $fetch; $this->orphanRemoval = $orphanRemoval; } } orm/lib/Doctrine/ORM/Mapping/OrderBy.php 0000644 00000001020 15120025734 0013755 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class OrderBy implements MappingAttribute { /** * @var array<string> * @readonly */ public $value; /** @param array<string> $value */ public function __construct(array $value) { $this->value = $value; } } orm/lib/Doctrine/ORM/Mapping/PostLoad.php 0000644 00000000332 15120025734 0014141 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PostLoad implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PostPersist.php 0000644 00000000335 15120025734 0014716 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PostPersist implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PostRemove.php 0000644 00000000334 15120025734 0014521 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PostRemove implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PostUpdate.php 0000644 00000000334 15120025734 0014506 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PostUpdate implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PreFlush.php 0000644 00000000332 15120025734 0014144 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PreFlush implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PrePersist.php 0000644 00000000334 15120025734 0014516 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PrePersist implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PreRemove.php 0000644 00000000333 15120025734 0014321 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PreRemove implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/PreUpdate.php 0000644 00000000333 15120025734 0014306 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("METHOD") */ #[Attribute(Attribute::TARGET_METHOD)] final class PreUpdate implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/QuoteStrategy.php 0000644 00000004517 15120025734 0015245 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * A set of rules for determining the column, alias and table quotes. * * @psalm-import-type AssociationMapping from ClassMetadata * @psalm-import-type JoinColumnData from ClassMetadata */ interface QuoteStrategy { /** * Gets the (possibly quoted) column name for safe use in an SQL statement. * * @param string $fieldName * * @return string */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) primary table name for safe use in an SQL statement. * * @return string */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) sequence name for safe use in an SQL statement. * * @param mixed[] $definition * * @return string */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) name of the join table. * * @param AssociationMapping $association * * @return string */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param JoinColumnData $joinColumn * * @return string */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) join column name. * * @param JoinColumnData $joinColumn * * @return string */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform); /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @psalm-return list<string> */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform); /** * Gets the column alias. * * @param string $columnName * @param int $counter * * @return string */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null); } orm/lib/Doctrine/ORM/Mapping/ReflectionEnumProperty.php 0000644 00000005420 15120025734 0017103 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use BackedEnum; use ReflectionProperty; use ReturnTypeWillChange; use ValueError; use function array_map; use function get_class; use function is_array; class ReflectionEnumProperty extends ReflectionProperty { /** @var ReflectionProperty */ private $originalReflectionProperty; /** @var class-string<BackedEnum> */ private $enumType; /** @param class-string<BackedEnum> $enumType */ public function __construct(ReflectionProperty $originalReflectionProperty, string $enumType) { $this->originalReflectionProperty = $originalReflectionProperty; $this->enumType = $enumType; parent::__construct( $originalReflectionProperty->getDeclaringClass()->getName(), $originalReflectionProperty->getName() ); } /** * {@inheritDoc} * * @param object|null $object * * @return int|string|int[]|string[]|null */ #[ReturnTypeWillChange] public function getValue($object = null) { if ($object === null) { return null; } $enum = $this->originalReflectionProperty->getValue($object); if ($enum === null) { return null; } if (is_array($enum)) { return array_map(static function (BackedEnum $item): mixed { return $item->value; }, $enum); } return $enum->value; } /** * @param object $object * @param int|string|int[]|string[]|BackedEnum|BackedEnum[]|null $value */ public function setValue($object, $value = null): void { if ($value !== null) { if (is_array($value)) { $value = array_map(function ($item) use ($object): BackedEnum { return $this->initializeEnumValue($object, $item); }, $value); } else { $value = $this->initializeEnumValue($object, $value); } } $this->originalReflectionProperty->setValue($object, $value); } /** * @param object $object * @param int|string|BackedEnum $value */ private function initializeEnumValue($object, $value): BackedEnum { if ($value instanceof BackedEnum) { return $value; } $enumType = $this->enumType; try { return $enumType::from($value); } catch (ValueError $e) { throw MappingException::invalidEnumValue( get_class($object), $this->originalReflectionProperty->getName(), (string) $value, $enumType, $e ); } } } orm/lib/Doctrine/ORM/Mapping/Reflection/ReflectionPropertiesGetter.php 0000644 00000007411 15120025734 0022035 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping\Reflection; use Doctrine\Persistence\Mapping\ReflectionService; use ReflectionClass; use ReflectionProperty; use function array_combine; use function array_filter; use function array_map; use function array_merge; /** * Utility class to retrieve all reflection instance properties of a given class, including * private inherited properties and transient properties. * * @private This API is for internal use only */ final class ReflectionPropertiesGetter { /** @var ReflectionProperty[][] indexed by class name and property internal name */ private $properties = []; /** @var ReflectionService */ private $reflectionService; public function __construct(ReflectionService $reflectionService) { $this->reflectionService = $reflectionService; } /** * @param string $className * @psalm-param class-string $className * * @return ReflectionProperty[] indexed by property internal name */ public function getProperties($className): array { if (isset($this->properties[$className])) { return $this->properties[$className]; } return $this->properties[$className] = array_merge( // first merge because `array_merge` expects >= 1 params ...array_merge( [[]], array_map( [$this, 'getClassProperties'], $this->getHierarchyClasses($className) ) ) ); } /** * @psalm-param class-string $className * * @return ReflectionClass[] * @psalm-return list<ReflectionClass<object>> */ private function getHierarchyClasses(string $className): array { $classes = []; $parentClassName = $className; while ($parentClassName && $currentClass = $this->reflectionService->getClass($parentClassName)) { $classes[] = $currentClass; $parentClassName = null; $parentClass = $currentClass->getParentClass(); if ($parentClass) { $parentClassName = $parentClass->getName(); } } return $classes; } // phpcs:disable SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod /** * @return ReflectionProperty[] * @psalm-return array<string, ReflectionProperty> */ private function getClassProperties(ReflectionClass $reflectionClass): array { // phpcs:enable SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod $properties = $reflectionClass->getProperties(); return array_filter( array_filter(array_map( [$this, 'getAccessibleProperty'], array_combine( array_map([$this, 'getLogicalName'], $properties), $properties ) )), [$this, 'isInstanceProperty'] ); } private function isInstanceProperty(ReflectionProperty $reflectionProperty): bool { return ! $reflectionProperty->isStatic(); } private function getAccessibleProperty(ReflectionProperty $property): ?ReflectionProperty { return $this->reflectionService->getAccessibleProperty( $property->getDeclaringClass()->getName(), $property->getName() ); } private function getLogicalName(ReflectionProperty $property): string { $propertyName = $property->getName(); if ($property->isPublic()) { return $propertyName; } if ($property->isProtected()) { return "\0*\0" . $propertyName; } return "\0" . $property->getDeclaringClass()->getName() . "\0" . $propertyName; } } orm/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php 0000644 00000001535 15120025734 0016350 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * The SqlResultSetMapping annotation is used to specify the mapping of the result of a native SQL query. * The SqlResultSetMapping annotation can be applied to an entity or mapped superclass. * * @Annotation * @Target("ANNOTATION") */ final class SqlResultSetMapping implements MappingAttribute { /** * The name given to the result set mapping, and used to refer to it in the methods of the Query API. * * @var string */ public $name; /** * Specifies the result set mapping to entities. * * @var array<\Doctrine\ORM\Mapping\EntityResult> */ public $entities = []; /** * Specifies the result set mapping to scalar values. * * @var array<\Doctrine\ORM\Mapping\ColumnResult> */ public $columns = []; } orm/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php 0000644 00000000726 15120025734 0016534 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * Is used to specify an array of mappings. * The SqlResultSetMappings annotation can be applied to an entity or mapped superclass. * * @Annotation * @Target("CLASS") */ final class SqlResultSetMappings implements MappingAttribute { /** * One or more SqlResultSetMapping annotations. * * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ public $value = []; } orm/lib/Doctrine/ORM/Mapping/Table.php 0000644 00000002534 15120025734 0013451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class Table implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var string|null * @readonly */ public $schema; /** * @var array<Index>|null * @readonly */ public $indexes; /** * @var array<UniqueConstraint>|null * @readonly */ public $uniqueConstraints; /** * @var array<string,mixed> * @readonly */ public $options = []; /** * @param array<Index>|null $indexes * @param array<UniqueConstraint>|null $uniqueConstraints * @param array<string,mixed> $options */ public function __construct( ?string $name = null, ?string $schema = null, ?array $indexes = null, ?array $uniqueConstraints = null, array $options = [] ) { $this->name = $name; $this->schema = $schema; $this->indexes = $indexes; $this->uniqueConstraints = $uniqueConstraints; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/TypedFieldMapper.php 0000644 00000001120 15120025734 0015606 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use BackedEnum; use ReflectionProperty; interface TypedFieldMapper { /** * Validates & completes the given field mapping based on typed property. * * @param array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} $mapping The field mapping to validate & complete. * * @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping. */ public function validateAndComplete(array $mapping, ReflectionProperty $field): array; } orm/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php 0000644 00000007211 15120025734 0017405 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\Deprecations\Deprecation; use function preg_replace; use function str_contains; use function strrpos; use function strtolower; use function strtoupper; use function substr; use const CASE_LOWER; use const CASE_UPPER; /** * Naming strategy implementing the underscore naming convention. * Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'. * * @link www.doctrine-project.org */ class UnderscoreNamingStrategy implements NamingStrategy { private const DEFAULT_PATTERN = '/(?<=[a-z])([A-Z])/'; private const NUMBER_AWARE_PATTERN = '/(?<=[a-z0-9])([A-Z])/'; /** @var int */ private $case; /** @var string */ private $pattern; /** * Underscore naming strategy construct. * * @param int $case CASE_LOWER | CASE_UPPER */ public function __construct($case = CASE_LOWER, bool $numberAware = false) { if (! $numberAware) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/7908', 'Creating %s without setting second argument $numberAware=true is deprecated and will be removed in Doctrine ORM 3.0.', self::class ); } $this->case = $case; $this->pattern = $numberAware ? self::NUMBER_AWARE_PATTERN : self::DEFAULT_PATTERN; } /** @return int CASE_LOWER | CASE_UPPER */ public function getCase() { return $this->case; } /** * Sets string case CASE_LOWER | CASE_UPPER. * Alphabetic characters converted to lowercase or uppercase. * * @param int $case * * @return void */ public function setCase($case) { $this->case = $case; } /** * {@inheritDoc} */ public function classToTableName($className) { if (str_contains($className, '\\')) { $className = substr($className, strrpos($className, '\\') + 1); } return $this->underscore($className); } /** * {@inheritDoc} */ public function propertyToColumnName($propertyName, $className = null) { return $this->underscore($propertyName); } /** * {@inheritDoc} */ public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null) { return $this->underscore($propertyName) . '_' . $embeddedColumnName; } /** * {@inheritDoc} */ public function referenceColumnName() { return $this->case === CASE_UPPER ? 'ID' : 'id'; } /** * {@inheritDoc} * * @param string $propertyName * @param class-string $className */ public function joinColumnName($propertyName, $className = null) { return $this->underscore($propertyName) . '_' . $this->referenceColumnName(); } /** * {@inheritDoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity); } /** * {@inheritDoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return $this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName()); } private function underscore(string $string): string { $string = preg_replace($this->pattern, '_$1', $string); if ($this->case === CASE_UPPER) { return strtoupper($string); } return strtolower($string); } } orm/lib/Doctrine/ORM/Mapping/UniqueConstraint.php 0000644 00000002211 15120025734 0015725 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("ANNOTATION") */ #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class UniqueConstraint implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var array<string>|null * @readonly */ public $columns; /** * @var array<string>|null * @readonly */ public $fields; /** * @var array<string,mixed>|null * @readonly */ public $options; /** * @param array<string>|null $columns * @param array<string>|null $fields * @param array<string,mixed>|null $options */ public function __construct( ?string $name = null, ?array $columns = null, ?array $fields = null, ?array $options = null ) { $this->name = $name; $this->columns = $columns; $this->fields = $fields; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/Version.php 0000644 00000000335 15120025734 0014044 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class Version implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/Annotation.php 0000644 00000000223 15120025734 0014525 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** @deprecated Use {@see MappingAttribute} instead. */ interface Annotation { } orm/lib/Doctrine/ORM/Mapping/AnsiQuoteStrategy.php 0000644 00000003750 15120025734 0016056 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Internal\SQLResultCasing; /** * ANSI compliant quote strategy, this strategy does not apply any quote. * To use this strategy all mapped tables and columns should be ANSI compliant. */ class AnsiQuoteStrategy implements QuoteStrategy { use SQLResultCasing; /** * {@inheritDoc} */ public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform) { return $class->fieldMappings[$fieldName]['columnName']; } /** * {@inheritDoc} */ public function getTableName(ClassMetadata $class, AbstractPlatform $platform) { return $class->table['name']; } /** * {@inheritDoc} */ public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform) { return $definition['sequenceName']; } /** * {@inheritDoc} */ public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return $joinColumn['name']; } /** * {@inheritDoc} */ public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform) { return $joinColumn['referencedColumnName']; } /** * {@inheritDoc} */ public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform) { return $association['joinTable']['name']; } /** * {@inheritDoc} */ public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform) { return $class->identifier; } /** * {@inheritDoc} */ public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null) { return $this->getSQLResultCasing($platform, $columnName . '_' . $counter); } } orm/lib/Doctrine/ORM/Mapping/AssociationOverride.php 0000644 00000004400 15120025734 0016370 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * This attribute is used to override association mapping of property for an entity relationship. * * @Annotation * @NamedArgumentConstructor * @Target("ANNOTATION") */ final class AssociationOverride implements MappingAttribute { /** * The name of the relationship property whose mapping is being overridden. * * @var string * @readonly */ public $name; /** * The join column that is being mapped to the persistent attribute. * * @var array<JoinColumn>|null * @readonly */ public $joinColumns; /** * The join column that is being mapped to the persistent attribute. * * @var array<JoinColumn>|null * @readonly */ public $inverseJoinColumns; /** * The join table that maps the relationship. * * @var JoinTable|null * @readonly */ public $joinTable; /** * The name of the association-field on the inverse-side. * * @var string|null * @readonly */ public $inversedBy; /** * The fetching strategy to use for the association. * * @var string|null * @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'|null * @readonly * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"}) */ public $fetch; /** * @param JoinColumn|array<JoinColumn> $joinColumns * @param JoinColumn|array<JoinColumn> $inverseJoinColumns * @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch */ public function __construct( string $name, $joinColumns = null, $inverseJoinColumns = null, ?JoinTable $joinTable = null, ?string $inversedBy = null, ?string $fetch = null ) { if ($joinColumns instanceof JoinColumn) { $joinColumns = [$joinColumns]; } if ($inverseJoinColumns instanceof JoinColumn) { $inverseJoinColumns = [$inverseJoinColumns]; } $this->name = $name; $this->joinColumns = $joinColumns; $this->inverseJoinColumns = $inverseJoinColumns; $this->joinTable = $joinTable; $this->inversedBy = $inversedBy; $this->fetch = $fetch; } } orm/lib/Doctrine/ORM/Mapping/AssociationOverrides.php 0000644 00000002045 15120025734 0016556 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use function array_values; use function is_array; /** * This attribute is used to override association mappings of relationship properties. * * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class AssociationOverrides implements MappingAttribute { /** * Mapping overrides of relationship properties. * * @var list<AssociationOverride> * @readonly */ public $overrides = []; /** @param array<AssociationOverride>|AssociationOverride $overrides */ public function __construct($overrides) { if (! is_array($overrides)) { $overrides = [$overrides]; } foreach ($overrides as $override) { if (! ($override instanceof AssociationOverride)) { throw MappingException::invalidOverrideType('AssociationOverride', $override); } } $this->overrides = array_values($overrides); } } orm/lib/Doctrine/ORM/Mapping/AttributeOverride.php 0000644 00000001241 15120025734 0016057 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * This attribute is used to override the mapping of a entity property. * * @Annotation * @NamedArgumentConstructor * @Target("ANNOTATION") */ final class AttributeOverride implements MappingAttribute { /** * The name of the property whose mapping is being overridden. * * @var string * @readonly */ public $name; /** * The column definition. * * @var Column * @readonly */ public $column; public function __construct(string $name, Column $column) { $this->name = $name; $this->column = $column; } } orm/lib/Doctrine/ORM/Mapping/AttributeOverrides.php 0000644 00000002015 15120025734 0016242 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use function array_values; use function is_array; /** * This attribute is used to override the mapping of a entity property. * * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class AttributeOverrides implements MappingAttribute { /** * One or more field or property mapping overrides. * * @var list<AttributeOverride> * @readonly */ public $overrides = []; /** @param array<AttributeOverride>|AttributeOverride $overrides */ public function __construct($overrides) { if (! is_array($overrides)) { $overrides = [$overrides]; } foreach ($overrides as $override) { if (! ($override instanceof AttributeOverride)) { throw MappingException::invalidOverrideType('AttributeOverride', $override); } } $this->overrides = array_values($overrides); } } orm/lib/Doctrine/ORM/Mapping/Cache.php 0000644 00000001665 15120025734 0013431 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * Caching to an entity or a collection. * * @Annotation * @NamedArgumentConstructor() * @Target({"CLASS","PROPERTY"}) */ #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)] final class Cache implements MappingAttribute { /** * The concurrency strategy. * * @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"}) * @var string * @psalm-var 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' */ public $usage = 'READ_ONLY'; /** @var string|null Cache region name. */ public $region; /** @psalm-param 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' $usage */ public function __construct(string $usage = 'READ_ONLY', ?string $region = null) { $this->usage = $usage; $this->region = $region; } } orm/lib/Doctrine/ORM/Mapping/ChainTypedFieldMapper.php 0000644 00000001345 15120025734 0016562 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use ReflectionProperty; final class ChainTypedFieldMapper implements TypedFieldMapper { /** * @readonly * @var TypedFieldMapper[] $typedFieldMappers */ private array $typedFieldMappers; public function __construct(TypedFieldMapper ...$typedFieldMappers) { $this->typedFieldMappers = $typedFieldMappers; } /** * {@inheritDoc} */ public function validateAndComplete(array $mapping, ReflectionProperty $field): array { foreach ($this->typedFieldMappers as $typedFieldMapper) { $mapping = $typedFieldMapper->validateAndComplete($mapping, $field); } return $mapping; } } orm/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php 0000644 00000001352 15120025734 0016447 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class ChangeTrackingPolicy implements MappingAttribute { /** * The change tracking policy. * * @var string * @psalm-var 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY' * @readonly * @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"}) */ public $value; /** @psalm-param 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY' $value */ public function __construct(string $value) { $this->value = $value; } } orm/lib/Doctrine/ORM/Mapping/ClassMetadata.php 0000644 00000006623 15120025734 0015133 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use BackedEnum; /** * {@inheritDoc} * * @todo remove or rename ClassMetadataInfo to ClassMetadata * @template-covariant T of object * @template-extends ClassMetadataInfo<T> * @psalm-type FieldMapping = array{ * type: string, * fieldName: string, * columnName: string, * length?: int, * id?: bool, * nullable?: bool, * notInsertable?: bool, * notUpdatable?: bool, * generated?: int, * enumType?: class-string<BackedEnum>, * columnDefinition?: string, * precision?: int, * scale?: int, * unique?: bool, * inherited?: class-string, * originalClass?: class-string, * originalField?: string, * quoted?: bool, * requireSQLConversion?: bool, * declared?: class-string, * declaredField?: string, * options?: array<string, mixed>, * version?: string, * default?: string|int, * } * @psalm-type JoinColumnData = array{ * name: string, * referencedColumnName: string, * unique?: bool, * quoted?: bool, * fieldName?: string, * onDelete?: string, * columnDefinition?: string, * nullable?: bool, * } * @psalm-type AssociationMapping = array{ * cache?: array, * cascade: array<string>, * declared?: class-string, * fetch: mixed, * fieldName: string, * id?: bool, * inherited?: class-string, * indexBy?: string, * inversedBy: string|null, * isCascadeRemove: bool, * isCascadePersist: bool, * isCascadeRefresh: bool, * isCascadeMerge: bool, * isCascadeDetach: bool, * isOnDeleteCascade?: bool, * isOwningSide: bool, * joinColumns?: array<JoinColumnData>, * joinColumnFieldNames?: array<string, string>, * joinTable?: array, * joinTableColumns?: list<mixed>, * mappedBy: string|null, * orderBy?: array, * originalClass?: class-string, * originalField?: string, * orphanRemoval?: bool, * relationToSourceKeyColumns?: array, * relationToTargetKeyColumns?: array, * sourceEntity: class-string, * sourceToTargetKeyColumns?: array<string, string>, * targetEntity: class-string, * targetToSourceKeyColumns?: array<string, string>, * type: int, * unique?: bool, * } * @psalm-type DiscriminatorColumnMapping = array{ * name: string, * fieldName: string, * type: string, * length?: int, * columnDefinition?: string|null, * enumType?: class-string<BackedEnum>|null, * options?: array<string, mixed>, * } * @psalm-type EmbeddedClassMapping = array{ * class: class-string, * columnPrefix: string|null, * declaredField: string|null, * originalField: string|null, * inherited?: class-string, * declared?: class-string, * } */ class ClassMetadata extends ClassMetadataInfo { /** * Repeating the ClassMetadataInfo constructor to infer correctly the template with PHPStan * * @see https://github.com/doctrine/orm/issues/8709 * * @param string $entityName The name of the entity class the new instance is used for. * @psalm-param class-string<T> $entityName */ public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null) { parent::__construct($entityName, $namingStrategy, $typedFieldMapper); } } orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php 0000644 00000076303 15120025734 0016465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\Common\EventManager; use Doctrine\DBAL\Platforms; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs; use Doctrine\ORM\Events; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\Id\BigIntegerIdentityGenerator; use Doctrine\ORM\Id\IdentityGenerator; use Doctrine\ORM\Id\SequenceGenerator; use Doctrine\ORM\Id\UuidGenerator; use Doctrine\ORM\Mapping\Exception\CannotGenerateIds; use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator; use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType; use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Mapping\ReflectionService; use ReflectionClass; use ReflectionException; use function assert; use function class_exists; use function count; use function end; use function explode; use function get_class; use function in_array; use function is_subclass_of; use function str_contains; use function strlen; use function strtolower; use function substr; /** * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the * metadata mapping information of a class which describes how a class should be mapped * to a relational database. * * @extends AbstractClassMetadataFactory<ClassMetadata> * @psalm-import-type AssociationMapping from ClassMetadata * @psalm-import-type EmbeddedClassMapping from ClassMetadata * @psalm-import-type FieldMapping from ClassMetadata */ class ClassMetadataFactory extends AbstractClassMetadataFactory { /** @var EntityManagerInterface|null */ private $em; /** @var AbstractPlatform|null */ private $targetPlatform; /** @var MappingDriver */ private $driver; /** @var EventManager */ private $evm; /** @var mixed[] */ private $embeddablesActiveNesting = []; /** @return void */ public function setEntityManager(EntityManagerInterface $em) { $this->em = $em; } /** * {@inheritDoc} */ protected function initialize() { $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); $this->evm = $this->em->getEventManager(); $this->initialized = true; } /** * {@inheritDoc} */ protected function onNotFoundMetadata($className) { if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { return null; } $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em); $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs); $classMetadata = $eventArgs->getFoundMetadata(); assert($classMetadata instanceof ClassMetadata || $classMetadata === null); return $classMetadata; } /** * {@inheritDoc} */ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents) { if ($parent) { $class->setInheritanceType($parent->inheritanceType); $class->setDiscriminatorColumn($parent->discriminatorColumn); $class->setIdGeneratorType($parent->generatorType); $this->addInheritedFields($class, $parent); $this->addInheritedRelations($class, $parent); $this->addInheritedEmbeddedClasses($class, $parent); $class->setIdentifier($parent->identifier); $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); $class->setDiscriminatorMap($parent->discriminatorMap); $class->addSubClasses($parent->subClasses); $class->setLifecycleCallbacks($parent->lifecycleCallbacks); $class->setChangeTrackingPolicy($parent->changeTrackingPolicy); if (! empty($parent->customGeneratorDefinition)) { $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition); } if ($parent->isMappedSuperclass) { $class->setCustomRepositoryClass($parent->customRepositoryClassName); } } // Invoke driver try { $this->driver->loadMetadataForClass($class->getName(), $class); } catch (ReflectionException $e) { throw MappingException::reflectionFailure($class->getName(), $e); } // If this class has a parent the id generator strategy is inherited. // However this is only true if the hierarchy of parents contains the root entity, // if it consists of mapped superclasses these don't necessarily include the id field. if ($parent && $rootEntityFound) { $this->inheritIdGeneratorMapping($class, $parent); } else { $this->completeIdGeneratorMapping($class); } if (! $class->isMappedSuperclass) { if ($rootEntityFound && $class->isInheritanceTypeNone()) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10431', "Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared. This is a misconfiguration and will be an error in Doctrine ORM 3.0.", $class->name, end($nonSuperclassParents) ); } foreach ($class->embeddedClasses as $property => $embeddableClass) { if (isset($embeddableClass['inherited'])) { continue; } if (isset($this->embeddablesActiveNesting[$embeddableClass['class']])) { throw MappingException::infiniteEmbeddableNesting($class->name, $property); } $this->embeddablesActiveNesting[$class->name] = true; $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); if ($embeddableMetadata->isEmbeddedClass) { $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property); } $identifier = $embeddableMetadata->getIdentifier(); if (! empty($identifier)) { $this->inheritIdGeneratorMapping($class, $embeddableMetadata); } $class->inlineEmbeddable($property, $embeddableMetadata); unset($this->embeddablesActiveNesting[$class->name]); } } if ($parent) { if ($parent->isInheritanceTypeSingleTable()) { $class->setPrimaryTable($parent->table); } $this->addInheritedIndexes($class, $parent); if ($parent->cache) { $class->cache = $parent->cache; } if ($parent->containsForeignIdentifier) { $class->containsForeignIdentifier = true; } if ($parent->containsEnumIdentifier) { $class->containsEnumIdentifier = true; } if (! empty($parent->namedQueries)) { $this->addInheritedNamedQueries($class, $parent); } if (! empty($parent->namedNativeQueries)) { $this->addInheritedNamedNativeQueries($class, $parent); } if (! empty($parent->sqlResultSetMappings)) { $this->addInheritedSqlResultSetMappings($class, $parent); } if (! empty($parent->entityListeners) && empty($class->entityListeners)) { $class->entityListeners = $parent->entityListeners; } } $class->setParentClasses($nonSuperclassParents); if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) { $this->addDefaultDiscriminatorMap($class); } // During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402. // So, we must not discover the missing subclasses before that. if ($this->evm->hasListeners(Events::loadClassMetadata)) { $eventArgs = new LoadClassMetadataEventArgs($class, $this->em); $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); } $this->findAbstractEntityClassesNotListedInDiscriminatorMap($class); if ($class->changeTrackingPolicy === ClassMetadata::CHANGETRACKING_NOTIFY) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8383', 'NOTIFY Change Tracking policy used in "%s" is deprecated, use deferred explicit instead.', $class->name ); } $this->validateRuntimeMetadata($class, $parent); } /** * Validate runtime metadata is correctly defined. * * @param ClassMetadata $class * @param ClassMetadataInterface|null $parent * * @return void * * @throws MappingException */ protected function validateRuntimeMetadata($class, $parent) { if (! $class->reflClass) { // only validate if there is a reflection class instance return; } $class->validateIdentifier(); $class->validateAssociations(); $class->validateLifecycleCallbacks($this->getReflectionService()); // verify inheritance if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) { if (! $parent) { if (count($class->discriminatorMap) === 0) { throw MappingException::missingDiscriminatorMap($class->name); } if (! $class->discriminatorColumn) { throw MappingException::missingDiscriminatorColumn($class->name); } foreach ($class->subClasses as $subClass) { if ((new ReflectionClass($subClass))->name !== $subClass) { throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name); } } } else { assert($parent instanceof ClassMetadataInfo); // https://github.com/doctrine/orm/issues/8746 if ( ! $class->reflClass->isAbstract() && ! in_array($class->name, $class->discriminatorMap, true) ) { throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName); } } } elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) { // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy throw MappingException::noInheritanceOnMappedSuperClass($class->name); } } /** * {@inheritDoc} */ protected function newClassMetadataInstance($className) { return new ClassMetadata( $className, $this->em->getConfiguration()->getNamingStrategy(), $this->em->getConfiguration()->getTypedFieldMapper() ); } /** * Adds a default discriminator map if no one is given * * If an entity is of any inheritance type and does not contain a * discriminator map, then the map is generated automatically. This process * is expensive computation wise. * * The automatically generated discriminator map contains the lowercase short name of * each class as key. * * @throws MappingException */ private function addDefaultDiscriminatorMap(ClassMetadata $class): void { $allClasses = $this->driver->getAllClassNames(); $fqcn = $class->getName(); $map = [$this->getShortName($class->name) => $fqcn]; $duplicates = []; foreach ($allClasses as $subClassCandidate) { if (is_subclass_of($subClassCandidate, $fqcn)) { $shortName = $this->getShortName($subClassCandidate); if (isset($map[$shortName])) { $duplicates[] = $shortName; } $map[$shortName] = $subClassCandidate; } } if ($duplicates) { throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map); } $class->setDiscriminatorMap($map); } private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void { // Only root classes in inheritance hierarchies need contain a discriminator map, // so skip for other classes. if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) { return; } $processedClasses = [$rootEntityClass->name => true]; foreach ($rootEntityClass->subClasses as $knownSubClass) { $processedClasses[$knownSubClass] = true; } foreach ($rootEntityClass->discriminatorMap as $declaredClassName) { // This fetches non-transient parent classes only $parentClasses = $this->getParentClasses($declaredClassName); foreach ($parentClasses as $parentClass) { if (isset($processedClasses[$parentClass])) { continue; } $processedClasses[$parentClass] = true; // All non-abstract entity classes must be listed in the discriminator map, and // this will be validated/enforced at runtime (possibly at a later time, when the // subclass is loaded, but anyways). Also, subclasses is about entity classes only. // That means we can ignore non-abstract classes here. The (expensive) driver // check for mapped superclasses need only be run for abstract candidate classes. if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) { continue; } // We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter) $rootEntityClass->addSubClass($parentClass); } } } /** @param class-string $className */ private function peekIfIsMappedSuperclass(string $className): bool { $reflService = $this->getReflectionService(); $class = $this->newClassMetadataInstance($className); $this->initializeReflection($class, $reflService); $this->driver->loadMetadataForClass($className, $class); return $class->isMappedSuperclass; } /** * Gets the lower-case short name of a class. * * @psalm-param class-string $className */ private function getShortName(string $className): string { if (! str_contains($className, '\\')) { return strtolower($className); } $parts = explode('\\', $className); return strtolower(end($parts)); } /** * Puts the `inherited` and `declared` values into mapping information for fields, associations * and embedded classes. * * @param AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping */ private function addMappingInheritanceInformation(array &$mapping, ClassMetadata $parentClass): void { if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) { $mapping['inherited'] = $parentClass->name; } if (! isset($mapping['declared'])) { $mapping['declared'] = $parentClass->name; } } /** * Adds inherited fields to the subclass mapping. */ private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->fieldMappings as $mapping) { $this->addMappingInheritanceInformation($mapping, $parentClass); $subClass->addInheritedFieldMapping($mapping); } foreach ($parentClass->reflFields as $name => $field) { $subClass->reflFields[$name] = $field; } } /** * Adds inherited association mappings to the subclass mapping. * * @throws MappingException */ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->associationMappings as $field => $mapping) { $this->addMappingInheritanceInformation($mapping, $parentClass); // When the class inheriting the relation ($subClass) is the first entity class since the // relation has been defined in a mapped superclass (or in a chain // of mapped superclasses) above, then declare this current entity class as the source of // the relationship. // According to the definitions given in https://github.com/doctrine/orm/pull/10396/, // this is the case <=> ! isset($mapping['inherited']). if (! isset($mapping['inherited'])) { $mapping['sourceEntity'] = $subClass->name; } $subClass->addInheritedAssociationMapping($mapping); } } private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->embeddedClasses as $field => $embeddedClass) { $this->addMappingInheritanceInformation($embeddedClass, $parentClass); $subClass->embeddedClasses[$field] = $embeddedClass; } } /** * Adds nested embedded classes metadata to a parent class. * * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from. * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to. * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names. */ private function addNestedEmbeddedClasses( ClassMetadata $subClass, ClassMetadata $parentClass, string $prefix ): void { foreach ($subClass->embeddedClasses as $property => $embeddableClass) { if (isset($embeddableClass['inherited'])) { continue; } $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); $parentClass->mapEmbedded( [ 'fieldName' => $prefix . '.' . $property, 'class' => $embeddableMetadata->name, 'columnPrefix' => $embeddableClass['columnPrefix'], 'declaredField' => $embeddableClass['declaredField'] ? $prefix . '.' . $embeddableClass['declaredField'] : $prefix, 'originalField' => $embeddableClass['originalField'] ?: $property, ] ); } } /** * Copy the table indices from the parent class superclass to the child class */ private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void { if (! $parentClass->isMappedSuperclass) { return; } foreach (['uniqueConstraints', 'indexes'] as $indexType) { if (isset($parentClass->table[$indexType])) { foreach ($parentClass->table[$indexType] as $indexName => $index) { if (isset($subClass->table[$indexType][$indexName])) { continue; // Let the inheriting table override indices } $subClass->table[$indexType][$indexName] = $index; } } } } /** * Adds inherited named queries to the subclass mapping. */ private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->namedQueries as $name => $query) { if (! isset($subClass->namedQueries[$name])) { $subClass->addNamedQuery( [ 'name' => $query['name'], 'query' => $query['query'], ] ); } } } /** * Adds inherited named native queries to the subclass mapping. */ private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->namedNativeQueries as $name => $query) { if (! isset($subClass->namedNativeQueries[$name])) { $subClass->addNamedNativeQuery( [ 'name' => $query['name'], 'query' => $query['query'], 'isSelfClass' => $query['isSelfClass'], 'resultSetMapping' => $query['resultSetMapping'], 'resultClass' => $query['isSelfClass'] ? $subClass->name : $query['resultClass'], ] ); } } } /** * Adds inherited sql result set mappings to the subclass mapping. */ private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass): void { foreach ($parentClass->sqlResultSetMappings as $name => $mapping) { if (! isset($subClass->sqlResultSetMappings[$name])) { $entities = []; foreach ($mapping['entities'] as $entity) { $entities[] = [ 'fields' => $entity['fields'], 'isSelfClass' => $entity['isSelfClass'], 'discriminatorColumn' => $entity['discriminatorColumn'], 'entityClass' => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'], ]; } $subClass->addSqlResultSetMapping( [ 'name' => $mapping['name'], 'columns' => $mapping['columns'], 'entities' => $entities, ] ); } } } /** * Completes the ID generator mapping. If "auto" is specified we choose the generator * most appropriate for the targeted database platform. * * @throws ORMException */ private function completeIdGeneratorMapping(ClassMetadataInfo $class): void { $idGenType = $class->generatorType; if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) { $class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform())); } // Create & assign an appropriate ID generator instance switch ($class->generatorType) { case ClassMetadata::GENERATOR_TYPE_IDENTITY: $sequenceName = null; $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null; $platform = $this->getTargetPlatform(); // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour. /** @psalm-suppress UndefinedClass, InvalidClass */ if (! $platform instanceof MySQLPlatform && ! $platform instanceof SqlitePlatform && ! $platform instanceof SQLServerPlatform && $platform->usesSequenceEmulatedIdentityColumns()) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8850', <<<'DEPRECATION' Context: Loading metadata for class %s Problem: Using the IDENTITY generator strategy with platform "%s" is deprecated and will not be possible in Doctrine ORM 3.0. Solution: Use the SEQUENCE generator strategy instead. DEPRECATION , $class->name, get_class($this->getTargetPlatform()) ); $columnName = $class->getSingleIdentifierColumnName(); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $sequencePrefix = $class->getSequencePrefix($this->getTargetPlatform()); $sequenceName = $this->getTargetPlatform()->getIdentitySequenceName($sequencePrefix, $columnName); $definition = [ 'sequenceName' => $this->truncateSequenceName($sequenceName), ]; if ($quoted) { $definition['quoted'] = true; } $sequenceName = $this ->em ->getConfiguration() ->getQuoteStrategy() ->getSequenceName($definition, $class, $this->getTargetPlatform()); } $generator = $fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint' ? new BigIntegerIdentityGenerator($sequenceName) : new IdentityGenerator($sequenceName); $class->setIdGenerator($generator); break; case ClassMetadata::GENERATOR_TYPE_SEQUENCE: // If there is no sequence definition yet, create a default definition $definition = $class->sequenceGeneratorDefinition; if (! $definition) { $fieldName = $class->getSingleIdentifierFieldName(); $sequenceName = $class->getSequenceName($this->getTargetPlatform()); $quoted = isset($class->fieldMappings[$fieldName]['quoted']) || isset($class->table['quoted']); $definition = [ 'sequenceName' => $this->truncateSequenceName($sequenceName), 'allocationSize' => 1, 'initialValue' => 1, ]; if ($quoted) { $definition['quoted'] = true; } $class->setSequenceGeneratorDefinition($definition); } $sequenceGenerator = new SequenceGenerator( $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()), (int) $definition['allocationSize'] ); $class->setIdGenerator($sequenceGenerator); break; case ClassMetadata::GENERATOR_TYPE_NONE: $class->setIdGenerator(new AssignedGenerator()); break; case ClassMetadata::GENERATOR_TYPE_UUID: Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/7312', 'Mapping for %s: the "UUID" id generator strategy is deprecated with no replacement', $class->name ); $class->setIdGenerator(new UuidGenerator()); break; case ClassMetadata::GENERATOR_TYPE_CUSTOM: $definition = $class->customGeneratorDefinition; if ($definition === null) { throw InvalidCustomGenerator::onClassNotConfigured(); } if (! class_exists($definition['class'])) { throw InvalidCustomGenerator::onMissingClass($definition); } $class->setIdGenerator(new $definition['class']()); break; default: throw UnknownGeneratorType::create($class->generatorType); } } /** @psalm-return ClassMetadata::GENERATOR_TYPE_SEQUENCE|ClassMetadata::GENERATOR_TYPE_IDENTITY */ private function determineIdGeneratorStrategy(AbstractPlatform $platform): int { if ( $platform instanceof Platforms\OraclePlatform || $platform instanceof Platforms\PostgreSQLPlatform ) { return ClassMetadata::GENERATOR_TYPE_SEQUENCE; } if ($platform->supportsIdentityColumns()) { return ClassMetadata::GENERATOR_TYPE_IDENTITY; } if ($platform->supportsSequences()) { return ClassMetadata::GENERATOR_TYPE_SEQUENCE; } throw CannotGenerateIds::withPlatform($platform); } private function truncateSequenceName(string $schemaElementName): string { $platform = $this->getTargetPlatform(); if (! $platform instanceof Platforms\OraclePlatform && ! $platform instanceof Platforms\SQLAnywherePlatform) { return $schemaElementName; } $maxIdentifierLength = $platform->getMaxIdentifierLength(); if (strlen($schemaElementName) > $maxIdentifierLength) { return substr($schemaElementName, 0, $maxIdentifierLength); } return $schemaElementName; } /** * Inherits the ID generator mapping from a parent class. */ private function inheritIdGeneratorMapping(ClassMetadataInfo $class, ClassMetadataInfo $parent): void { if ($parent->isIdGeneratorSequence()) { $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition); } if ($parent->generatorType) { $class->setIdGeneratorType($parent->generatorType); } if ($parent->idGenerator) { $class->setIdGenerator($parent->idGenerator); } } /** * {@inheritDoc} */ protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService) { assert($class instanceof ClassMetadata); $class->wakeupReflection($reflService); } /** * {@inheritDoc} */ protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService) { assert($class instanceof ClassMetadata); $class->initializeReflection($reflService); } /** * @deprecated This method will be removed in ORM 3.0. * * @return class-string */ protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) { /** @psalm-var class-string */ return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } /** * {@inheritDoc} */ protected function getDriver() { return $this->driver; } /** * {@inheritDoc} */ protected function isEntity(ClassMetadataInterface $class) { return ! $class->isMappedSuperclass; } private function getTargetPlatform(): Platforms\AbstractPlatform { if (! $this->targetPlatform) { $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); } return $this->targetPlatform; } } orm/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php 0000644 00000366155 15120025734 0015760 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use BackedEnum; use BadMethodCallException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\Instantiator\Instantiator; use Doctrine\Instantiator\InstantiatorInterface; use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Id\AbstractIdGenerator; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\ReflectionService; use InvalidArgumentException; use LogicException; use ReflectionClass; use ReflectionNamedType; use ReflectionProperty; use RuntimeException; use function array_diff; use function array_flip; use function array_intersect; use function array_keys; use function array_map; use function array_merge; use function array_pop; use function array_values; use function assert; use function class_exists; use function count; use function enum_exists; use function explode; use function gettype; use function in_array; use function interface_exists; use function is_array; use function is_subclass_of; use function ltrim; use function method_exists; use function spl_object_id; use function str_contains; use function str_replace; use function strtolower; use function trait_exists; use function trim; use const PHP_VERSION_ID; /** * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata * of an entity and its associations. * * Once populated, ClassMetadata instances are usually cached in a serialized form. * * <b>IMPORTANT NOTE:</b> * * The fields of this class are only public for 2 reasons: * 1) To allow fast READ access. * 2) To drastically reduce the size of a serialized instance (private/protected members * get the whole class name, namespace inclusive, prepended to every property in * the serialized representation). * * @template-covariant T of object * @template-implements ClassMetadata<T> * @psalm-import-type AssociationMapping from \Doctrine\ORM\Mapping\ClassMetadata * @psalm-import-type FieldMapping from \Doctrine\ORM\Mapping\ClassMetadata * @psalm-import-type EmbeddedClassMapping from \Doctrine\ORM\Mapping\ClassMetadata * @psalm-import-type JoinColumnData from \Doctrine\ORM\Mapping\ClassMetadata * @psalm-import-type DiscriminatorColumnMapping from \Doctrine\ORM\Mapping\ClassMetadata */ class ClassMetadataInfo implements ClassMetadata { /* The inheritance mapping types */ /** * NONE means the class does not participate in an inheritance hierarchy * and therefore does not need an inheritance mapping type. */ public const INHERITANCE_TYPE_NONE = 1; /** * JOINED means the class will be persisted according to the rules of * <tt>Class Table Inheritance</tt>. */ public const INHERITANCE_TYPE_JOINED = 2; /** * SINGLE_TABLE means the class will be persisted according to the rules of * <tt>Single Table Inheritance</tt>. */ public const INHERITANCE_TYPE_SINGLE_TABLE = 3; /** * TABLE_PER_CLASS means the class will be persisted according to the rules * of <tt>Concrete Table Inheritance</tt>. * * @deprecated */ public const INHERITANCE_TYPE_TABLE_PER_CLASS = 4; /* The Id generator types. */ /** * AUTO means the generator type will depend on what the used platform prefers. * Offers full portability. */ public const GENERATOR_TYPE_AUTO = 1; /** * SEQUENCE means a separate sequence object will be used. Platforms that do * not have native sequence support may emulate it. Full portability is currently * not guaranteed. */ public const GENERATOR_TYPE_SEQUENCE = 2; /** * TABLE means a separate table is used for id generation. * Offers full portability (in that it results in an exception being thrown * no matter the platform). * * @deprecated no replacement planned */ public const GENERATOR_TYPE_TABLE = 3; /** * IDENTITY means an identity column is used for id generation. The database * will fill in the id column on insertion. Platforms that do not support * native identity columns may emulate them. Full portability is currently * not guaranteed. */ public const GENERATOR_TYPE_IDENTITY = 4; /** * NONE means the class does not have a generated id. That means the class * must have a natural, manually assigned id. */ public const GENERATOR_TYPE_NONE = 5; /** * UUID means that a UUID/GUID expression is used for id generation. Full * portability is currently not guaranteed. * * @deprecated use an application-side generator instead */ public const GENERATOR_TYPE_UUID = 6; /** * CUSTOM means that customer will use own ID generator that supposedly work */ public const GENERATOR_TYPE_CUSTOM = 7; /** * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done for all entities that are in MANAGED state at commit-time. * * This is the default change tracking policy. */ public const CHANGETRACKING_DEFERRED_IMPLICIT = 1; /** * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done only for entities that were explicitly saved (through persist() or a cascade). */ public const CHANGETRACKING_DEFERRED_EXPLICIT = 2; /** * NOTIFY means that Doctrine relies on the entities sending out notifications * when their properties change. Such entity classes must implement * the <tt>NotifyPropertyChanged</tt> interface. */ public const CHANGETRACKING_NOTIFY = 3; /** * Specifies that an association is to be fetched when it is first accessed. */ public const FETCH_LAZY = 2; /** * Specifies that an association is to be fetched when the owner of the * association is fetched. */ public const FETCH_EAGER = 3; /** * Specifies that an association is to be fetched lazy (on first access) and that * commands such as Collection#count, Collection#slice are issued directly against * the database if the collection is not yet initialized. */ public const FETCH_EXTRA_LAZY = 4; /** * Identifies a one-to-one association. */ public const ONE_TO_ONE = 1; /** * Identifies a many-to-one association. */ public const MANY_TO_ONE = 2; /** * Identifies a one-to-many association. */ public const ONE_TO_MANY = 4; /** * Identifies a many-to-many association. */ public const MANY_TO_MANY = 8; /** * Combined bitmask for to-one (single-valued) associations. */ public const TO_ONE = 3; /** * Combined bitmask for to-many (collection-valued) associations. */ public const TO_MANY = 12; /** * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks, */ public const CACHE_USAGE_READ_ONLY = 1; /** * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes. */ public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; /** * Read Write Attempts to lock the entity before update/delete. */ public const CACHE_USAGE_READ_WRITE = 3; /** * The value of this column is never generated by the database. */ public const GENERATED_NEVER = 0; /** * The value of this column is generated by the database on INSERT, but not on UPDATE. */ public const GENERATED_INSERT = 1; /** * The value of this column is generated by the database on both INSERT and UDPATE statements. */ public const GENERATED_ALWAYS = 2; /** * READ-ONLY: The name of the entity class. * * @var string * @psalm-var class-string<T> */ public $name; /** * READ-ONLY: The namespace the entity class is contained in. * * @var string * @todo Not really needed. Usage could be localized. */ public $namespace; /** * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same * as {@link $name}. * * @var string * @psalm-var class-string */ public $rootEntityName; /** * READ-ONLY: The definition of custom generator. Only used for CUSTOM * generator type * * The definition has the following structure: * <code> * array( * 'class' => 'ClassName', * ) * </code> * * @todo Merge with tableGeneratorDefinition into generic generatorDefinition * @var array<string, string>|null */ public $customGeneratorDefinition; /** * The name of the custom repository class used for the entity class. * (Optional). * * @var string|null * @psalm-var ?class-string<EntityRepository> */ public $customRepositoryClassName; /** * READ-ONLY: Whether this class describes the mapping of a mapped superclass. * * @var bool */ public $isMappedSuperclass = false; /** * READ-ONLY: Whether this class describes the mapping of an embeddable class. * * @var bool */ public $isEmbeddedClass = false; /** * READ-ONLY: The names of the parent <em>entity</em> classes (ancestors), starting with the * nearest one and ending with the root entity class. * * @psalm-var list<class-string> */ public $parentClasses = []; /** * READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all * <em>entity</em> subclasses of this class. These may also be abstract classes. * * This list is used, for example, to enumerate all necessary tables in JTI when querying for root * or subclass entities, or to gather all fields comprised in an entity inheritance tree. * * For classes that do not use STI/JTI, this list is empty. * * Implementation note: * * In PHP, there is no general way to discover all subclasses of a given class at runtime. For that * reason, the list of classes given in the discriminator map at the root entity is considered * authoritative. The discriminator map must contain all <em>concrete</em> classes that can * appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract * entity classes, users are not required to list such classes with a discriminator value. * * The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the * root entity has been loaded. * * For subclasses of such root entities, the list can be reused/passed downwards, it only needs to * be filtered accordingly (only keep remaining subclasses) * * @psalm-var list<class-string> */ public $subClasses = []; /** * READ-ONLY: The names of all embedded classes based on properties. * * The value (definition) array may contain, among others, the following values: * * - <b>'inherited'</b> (string, optional) * This is set when this embedded-class field is inherited by this class from another (inheritance) parent * <em>entity</em> class. The value is the FQCN of the topmost entity class that contains * mapping information for this field. (If there are transient classes in the * class hierarchy, these are ignored, so the class property may in fact come * from a class further up in the PHP class hierarchy.) * Fields initially declared in mapped superclasses are * <em>not</em> considered 'inherited' in the nearest entity subclasses. * * - <b>'declared'</b> (string, optional) * This is set when the embedded-class field does not appear for the first time in this class, but is originally * declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN * of the topmost non-transient class that contains mapping information for this field. * * @psalm-var array<string, EmbeddedClassMapping> */ public $embeddedClasses = []; /** * READ-ONLY: The named queries allowed to be called directly from Repository. * * @psalm-var array<string, array<string, mixed>> */ public $namedQueries = []; /** * READ-ONLY: The named native queries allowed to be called directly from Repository. * * A native SQL named query definition has the following structure: * <pre> * array( * 'name' => <query name>, * 'query' => <sql query>, * 'resultClass' => <class of the result>, * 'resultSetMapping' => <name of a SqlResultSetMapping> * ) * </pre> * * @psalm-var array<string, array<string, mixed>> */ public $namedNativeQueries = []; /** * READ-ONLY: The mappings of the results of native SQL queries. * * A native result mapping definition has the following structure: * <pre> * array( * 'name' => <result name>, * 'entities' => array(<entity result mapping>), * 'columns' => array(<column result mapping>) * ) * </pre> * * @psalm-var array<string, array{ * name: string, * entities: mixed[], * columns: mixed[] * }> */ public $sqlResultSetMappings = []; /** * READ-ONLY: The field names of all fields that are part of the identifier/primary key * of the mapped entity class. * * @psalm-var list<string> */ public $identifier = []; /** * READ-ONLY: The inheritance mapping type used by the class. * * @var int * @psalm-var self::INHERITANCE_TYPE_* */ public $inheritanceType = self::INHERITANCE_TYPE_NONE; /** * READ-ONLY: The Id generator type used by the class. * * @var int * @psalm-var self::GENERATOR_TYPE_* */ public $generatorType = self::GENERATOR_TYPE_NONE; /** * READ-ONLY: The field mappings of the class. * Keys are field names and values are mapping definitions. * * The mapping definition array has the following values: * * - <b>fieldName</b> (string) * The name of the field in the Entity. * * - <b>type</b> (string) * The type name of the mapped field. Can be one of Doctrine's mapping types * or a custom mapping type. * * - <b>columnName</b> (string, optional) * The column name. Optional. Defaults to the field name. * * - <b>length</b> (integer, optional) * The database length of the column. Optional. Default value taken from * the type. * * - <b>id</b> (boolean, optional) * Marks the field as the primary key of the entity. Multiple fields of an * entity can have the id attribute, forming a composite key. * * - <b>nullable</b> (boolean, optional) * Whether the column is nullable. Defaults to FALSE. * * - <b>'notInsertable'</b> (boolean, optional) * Whether the column is not insertable. Optional. Is only set if value is TRUE. * * - <b>'notUpdatable'</b> (boolean, optional) * Whether the column is updatable. Optional. Is only set if value is TRUE. * * - <b>columnDefinition</b> (string, optional, schema-only) * The SQL fragment that is used when generating the DDL for the column. * * - <b>precision</b> (integer, optional, schema-only) * The precision of a decimal column. Only valid if the column type is decimal. * * - <b>scale</b> (integer, optional, schema-only) * The scale of a decimal column. Only valid if the column type is decimal. * * - <b>'unique'</b> (boolean, optional, schema-only) * Whether a unique constraint should be generated for the column. * * - <b>'inherited'</b> (string, optional) * This is set when the field is inherited by this class from another (inheritance) parent * <em>entity</em> class. The value is the FQCN of the topmost entity class that contains * mapping information for this field. (If there are transient classes in the * class hierarchy, these are ignored, so the class property may in fact come * from a class further up in the PHP class hierarchy.) * Fields initially declared in mapped superclasses are * <em>not</em> considered 'inherited' in the nearest entity subclasses. * * - <b>'declared'</b> (string, optional) * This is set when the field does not appear for the first time in this class, but is originally * declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN * of the topmost non-transient class that contains mapping information for this field. * * @var mixed[] * @psalm-var array<string, FieldMapping> */ public $fieldMappings = []; /** * READ-ONLY: An array of field names. Used to look up field names from column names. * Keys are column names and values are field names. * * @psalm-var array<string, string> */ public $fieldNames = []; /** * READ-ONLY: A map of field names to column names. Keys are field names and values column names. * Used to look up column names from field names. * This is the reverse lookup map of $_fieldNames. * * @deprecated 3.0 Remove this. * * @var mixed[] */ public $columnNames = []; /** * READ-ONLY: The discriminator value of this class. * * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used.</b> * * @see discriminatorColumn * * @var mixed */ public $discriminatorValue; /** * READ-ONLY: The discriminator map of all mapped classes in the hierarchy. * * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies * where a discriminator column is used.</b> * * @see discriminatorColumn * * @var array<int|string, string> * * @psalm-var array<int|string, class-string> */ public $discriminatorMap = []; /** * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE * inheritance mappings. * * @var array<string, mixed> * @psalm-var DiscriminatorColumnMapping|null */ public $discriminatorColumn; /** * READ-ONLY: The primary table definition. The definition is an array with the * following entries: * * name => <tableName> * schema => <schemaName> * indexes => array * uniqueConstraints => array * * @var mixed[] * @psalm-var array{ * name: string, * schema?: string, * indexes?: array, * uniqueConstraints?: array, * options?: array<string, mixed>, * quoted?: bool * } */ public $table; /** * READ-ONLY: The registered lifecycle callbacks for entities of this class. * * @psalm-var array<string, list<string>> */ public $lifecycleCallbacks = []; /** * READ-ONLY: The registered entity listeners. * * @psalm-var array<string, list<array{class: class-string, method: string}>> */ public $entityListeners = []; /** * READ-ONLY: The association mappings of this class. * * The mapping definition array supports the following keys: * * - <b>fieldName</b> (string) * The name of the field in the entity the association is mapped to. * * - <b>sourceEntity</b> (string) * The class name of the source entity. In the case of to-many associations initially * present in mapped superclasses, the nearest <em>entity</em> subclasses will be * considered the respective source entities. * * - <b>targetEntity</b> (string) * The class name of the target entity. If it is fully-qualified it is used as is. * If it is a simple, unqualified class name the namespace is assumed to be the same * as the namespace of the source entity. * * - <b>mappedBy</b> (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the owning side. * This key must be specified on the inverse side of a bidirectional association. * * - <b>inversedBy</b> (string, required for bidirectional associations) * The name of the field that completes the bidirectional association on the inverse side. * This key must be specified on the owning side of a bidirectional association. * * - <b>cascade</b> (array, optional) * The names of persistence operations to cascade on the association. The set of possible * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others). * * - <b>orderBy</b> (array, one-to-many/many-to-many only) * A map of field names (of the target entity) to sorting directions (ASC/DESC). * Example: array('priority' => 'desc') * * - <b>fetch</b> (integer, optional) * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY. * * - <b>joinTable</b> (array, optional, many-to-many only) * Specification of the join table and its join columns (foreign keys). * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped * through a join table by simply mapping the association as many-to-many with a unique * constraint on the join table. * * - <b>indexBy</b> (string, optional, to-many only) * Specification of a field on target-entity that is used to index the collection by. * This field HAS to be either the primary key or a unique column. Otherwise the collection * does not contain all the entities that are actually related. * * - <b>'inherited'</b> (string, optional) * This is set when the association is inherited by this class from another (inheritance) parent * <em>entity</em> class. The value is the FQCN of the topmost entity class that contains * this association. (If there are transient classes in the * class hierarchy, these are ignored, so the class property may in fact come * from a class further up in the PHP class hierarchy.) * To-many associations initially declared in mapped superclasses are * <em>not</em> considered 'inherited' in the nearest entity subclasses. * * - <b>'declared'</b> (string, optional) * This is set when the association does not appear in the current class for the first time, but * is initially declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN * of the topmost non-transient class that contains association information for this relationship. * * A join table definition has the following structure: * <pre> * array( * 'name' => <join table name>, * 'joinColumns' => array(<join column mapping from join table to source table>), * 'inverseJoinColumns' => array(<join column mapping from join table to target table>) * ) * </pre> * * @psalm-var array<string, AssociationMapping> */ public $associationMappings = []; /** * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite. * * @var bool */ public $isIdentifierComposite = false; /** * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association. * * This flag is necessary because some code blocks require special treatment of this cases. * * @var bool */ public $containsForeignIdentifier = false; /** * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type. * * This flag is necessary because some code blocks require special treatment of this cases. * * @var bool */ public $containsEnumIdentifier = false; /** * READ-ONLY: The ID generator used for generating IDs for this class. * * @var AbstractIdGenerator * @todo Remove! */ public $idGenerator; /** * READ-ONLY: The definition of the sequence generator of this class. Only used for the * SEQUENCE generation strategy. * * The definition has the following structure: * <code> * array( * 'sequenceName' => 'name', * 'allocationSize' => '20', * 'initialValue' => '1' * ) * </code> * * @var array<string, mixed>|null * @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}|null * @todo Merge with tableGeneratorDefinition into generic generatorDefinition */ public $sequenceGeneratorDefinition; /** * READ-ONLY: The definition of the table generator of this class. Only used for the * TABLE generation strategy. * * @deprecated * * @var array<string, mixed> */ public $tableGeneratorDefinition; /** * READ-ONLY: The policy used for change-tracking on entities of this class. * * @var int */ public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; /** * READ-ONLY: A Flag indicating whether one or more columns of this class * have to be reloaded after insert / update operations. * * @var bool */ public $requiresFetchAfterChange = false; /** * READ-ONLY: A flag for whether or not instances of this class are to be versioned * with optimistic locking. * * @var bool */ public $isVersioned = false; /** * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). * * @var string|null */ public $versionField; /** @var mixed[]|null */ public $cache; /** * The ReflectionClass instance of the mapped class. * * @var ReflectionClass|null */ public $reflClass; /** * Is this entity marked as "read-only"? * * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance * optimization for entities that are immutable, either in your domain or through the relation database * (coming from a view, or a history table for example). * * @var bool */ public $isReadOnly = false; /** * NamingStrategy determining the default column and table names. * * @var NamingStrategy */ protected $namingStrategy; /** * The ReflectionProperty instances of the mapped class. * * @var array<string, ReflectionProperty|null> */ public $reflFields = []; /** @var InstantiatorInterface|null */ private $instantiator; /** @var TypedFieldMapper $typedFieldMapper */ private $typedFieldMapper; /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param string $entityName The name of the entity class the new instance is used for. * @psalm-param class-string<T> $entityName */ public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null) { $this->name = $entityName; $this->rootEntityName = $entityName; $this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy(); $this->instantiator = new Instantiator(); $this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper(); } /** * Gets the ReflectionProperties of the mapped class. * * @return ReflectionProperty[]|null[] An array of ReflectionProperty instances. * @psalm-return array<ReflectionProperty|null> */ public function getReflectionProperties() { return $this->reflFields; } /** * Gets a ReflectionProperty for a specific field of the mapped class. * * @param string $name * * @return ReflectionProperty */ public function getReflectionProperty($name) { return $this->reflFields[$name]; } /** * Gets the ReflectionProperty for the single identifier field. * * @return ReflectionProperty * * @throws BadMethodCallException If the class has a composite identifier. */ public function getSingleIdReflectionProperty() { if ($this->isIdentifierComposite) { throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.'); } return $this->reflFields[$this->identifier[0]]; } /** * Extracts the identifier values of an entity of this class. * * For composite identifiers, the identifier values are returned as an array * with the same order as the field order in {@link identifier}. * * @param object $entity * * @return array<string, mixed> */ public function getIdentifierValues($entity) { if ($this->isIdentifierComposite) { $id = []; foreach ($this->identifier as $idField) { $value = $this->reflFields[$idField]->getValue($entity); if ($value !== null) { $id[$idField] = $value; } } return $id; } $id = $this->identifier[0]; $value = $this->reflFields[$id]->getValue($entity); if ($value === null) { return []; } return [$id => $value]; } /** * Populates the entity identifier of an entity. * * @param object $entity * @psalm-param array<string, mixed> $id * * @return void * * @todo Rename to assignIdentifier() */ public function setIdentifierValues($entity, array $id) { foreach ($id as $idField => $idValue) { $this->reflFields[$idField]->setValue($entity, $idValue); } } /** * Sets the specified field to the specified value on the given entity. * * @param object $entity * @param string $field * @param mixed $value * * @return void */ public function setFieldValue($entity, $field, $value) { $this->reflFields[$field]->setValue($entity, $value); } /** * Gets the specified field's value off the given entity. * * @param object $entity * @param string $field * * @return mixed */ public function getFieldValue($entity, $field) { return $this->reflFields[$field]->getValue($entity); } /** * Creates a string representation of this instance. * * @return string The string representation of this instance. * * @todo Construct meaningful string representation. */ public function __toString() { return self::class . '@' . spl_object_id($this); } /** * Determines which fields get serialized. * * It is only serialized what is necessary for best unserialization performance. * That means any metadata properties that are not set or empty or simply have * their default value are NOT serialized. * * Parts that are also NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) * * @return string[] The names of all the fields that should be serialized. */ public function __sleep() { // This metadata is always serialized/cached. $serialized = [ 'associationMappings', 'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName'] 'fieldMappings', 'fieldNames', 'embeddedClasses', 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', 'namespace', // TODO: REMOVE 'table', 'rootEntityName', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. ]; // The rest of the metadata is only serialized if necessary. if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) { $serialized[] = 'changeTrackingPolicy'; } if ($this->customRepositoryClassName) { $serialized[] = 'customRepositoryClassName'; } if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE) { $serialized[] = 'inheritanceType'; $serialized[] = 'discriminatorColumn'; $serialized[] = 'discriminatorValue'; $serialized[] = 'discriminatorMap'; $serialized[] = 'parentClasses'; $serialized[] = 'subClasses'; } if ($this->generatorType !== self::GENERATOR_TYPE_NONE) { $serialized[] = 'generatorType'; if ($this->generatorType === self::GENERATOR_TYPE_SEQUENCE) { $serialized[] = 'sequenceGeneratorDefinition'; } } if ($this->isMappedSuperclass) { $serialized[] = 'isMappedSuperclass'; } if ($this->isEmbeddedClass) { $serialized[] = 'isEmbeddedClass'; } if ($this->containsForeignIdentifier) { $serialized[] = 'containsForeignIdentifier'; } if ($this->containsEnumIdentifier) { $serialized[] = 'containsEnumIdentifier'; } if ($this->isVersioned) { $serialized[] = 'isVersioned'; $serialized[] = 'versionField'; } if ($this->lifecycleCallbacks) { $serialized[] = 'lifecycleCallbacks'; } if ($this->entityListeners) { $serialized[] = 'entityListeners'; } if ($this->namedQueries) { $serialized[] = 'namedQueries'; } if ($this->namedNativeQueries) { $serialized[] = 'namedNativeQueries'; } if ($this->sqlResultSetMappings) { $serialized[] = 'sqlResultSetMappings'; } if ($this->isReadOnly) { $serialized[] = 'isReadOnly'; } if ($this->customGeneratorDefinition) { $serialized[] = 'customGeneratorDefinition'; } if ($this->cache) { $serialized[] = 'cache'; } if ($this->requiresFetchAfterChange) { $serialized[] = 'requiresFetchAfterChange'; } return $serialized; } /** * Creates a new instance of the mapped class, without invoking the constructor. * * @return object */ public function newInstance() { return $this->instantiator->instantiate($this->name); } /** * Restores some state that can not be serialized/unserialized. * * @param ReflectionService $reflService * * @return void */ public function wakeupReflection($reflService) { // Restore ReflectionClass and properties $this->reflClass = $reflService->getClass($this->name); $this->instantiator = $this->instantiator ?: new Instantiator(); $parentReflFields = []; foreach ($this->embeddedClasses as $property => $embeddedClass) { if (isset($embeddedClass['declaredField'])) { assert($embeddedClass['originalField'] !== null); $childProperty = $this->getAccessibleProperty( $reflService, $this->embeddedClasses[$embeddedClass['declaredField']]['class'], $embeddedClass['originalField'] ); assert($childProperty !== null); $parentReflFields[$property] = new ReflectionEmbeddedProperty( $parentReflFields[$embeddedClass['declaredField']], $childProperty, $this->embeddedClasses[$embeddedClass['declaredField']]['class'] ); continue; } $fieldRefl = $this->getAccessibleProperty( $reflService, $embeddedClass['declared'] ?? $this->name, $property ); $parentReflFields[$property] = $fieldRefl; $this->reflFields[$property] = $fieldRefl; } foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) { $childProperty = $this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']); assert($childProperty !== null); if (isset($mapping['enumType'])) { $childProperty = new ReflectionEnumProperty( $childProperty, $mapping['enumType'] ); } $this->reflFields[$field] = new ReflectionEmbeddedProperty( $parentReflFields[$mapping['declaredField']], $childProperty, $mapping['originalClass'] ); continue; } $this->reflFields[$field] = isset($mapping['declared']) ? $this->getAccessibleProperty($reflService, $mapping['declared'], $field) : $this->getAccessibleProperty($reflService, $this->name, $field); if (isset($mapping['enumType']) && $this->reflFields[$field] !== null) { $this->reflFields[$field] = new ReflectionEnumProperty( $this->reflFields[$field], $mapping['enumType'] ); } } foreach ($this->associationMappings as $field => $mapping) { $this->reflFields[$field] = isset($mapping['declared']) ? $this->getAccessibleProperty($reflService, $mapping['declared'], $field) : $this->getAccessibleProperty($reflService, $this->name, $field); } } /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param ReflectionService $reflService The reflection service. * * @return void */ public function initializeReflection($reflService) { $this->reflClass = $reflService->getClass($this->name); $this->namespace = $reflService->getClassNamespace($this->name); if ($this->reflClass) { $this->name = $this->rootEntityName = $this->reflClass->getName(); } $this->table['name'] = $this->namingStrategy->classToTableName($this->name); } /** * Validates Identifier. * * @return void * * @throws MappingException */ public function validateIdentifier() { if ($this->isMappedSuperclass || $this->isEmbeddedClass) { return; } // Verify & complete identifier mapping if (! $this->identifier) { throw MappingException::identifierRequired($this->name); } if ($this->usesIdGenerator() && $this->isIdentifierComposite) { throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name); } } /** * Validates association targets actually exist. * * @return void * * @throws MappingException */ public function validateAssociations() { foreach ($this->associationMappings as $mapping) { if ( ! class_exists($mapping['targetEntity']) && ! interface_exists($mapping['targetEntity']) && ! trait_exists($mapping['targetEntity']) ) { throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']); } } } /** * Validates lifecycle callbacks. * * @param ReflectionService $reflService * * @return void * * @throws MappingException */ public function validateLifecycleCallbacks($reflService) { foreach ($this->lifecycleCallbacks as $callbacks) { foreach ($callbacks as $callbackFuncName) { if (! $reflService->hasPublicMethod($this->name, $callbackFuncName)) { throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName); } } } } /** * {@inheritDoc} */ public function getReflectionClass() { return $this->reflClass; } /** * @psalm-param array{usage?: mixed, region?: mixed} $cache * * @return void */ public function enableCache(array $cache) { if (! isset($cache['usage'])) { $cache['usage'] = self::CACHE_USAGE_READ_ONLY; } if (! isset($cache['region'])) { $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)); } $this->cache = $cache; } /** * @param string $fieldName * @psalm-param array{usage?: int, region?: string} $cache * * @return void */ public function enableAssociationCache($fieldName, array $cache) { $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache); } /** * @param string $fieldName * @param array $cache * @psalm-param array{usage?: int|null, region?: string|null} $cache * * @return int[]|string[] * @psalm-return array{usage: int, region: string|null} */ public function getAssociationCacheDefaults($fieldName, array $cache) { if (! isset($cache['usage'])) { $cache['usage'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY; } if (! isset($cache['region'])) { $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName; } return $cache; } /** * Sets the change tracking policy used by this class. * * @param int $policy * * @return void */ public function setChangeTrackingPolicy($policy) { $this->changeTrackingPolicy = $policy; } /** * Whether the change tracking policy of this class is "deferred explicit". * * @return bool */ public function isChangeTrackingDeferredExplicit() { return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT; } /** * Whether the change tracking policy of this class is "deferred implicit". * * @return bool */ public function isChangeTrackingDeferredImplicit() { return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT; } /** * Whether the change tracking policy of this class is "notify". * * @return bool */ public function isChangeTrackingNotify() { return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY; } /** * Checks whether a field is part of the identifier/primary key field(s). * * @param string $fieldName The field name. * * @return bool TRUE if the field is part of the table identifier/primary key field(s), * FALSE otherwise. */ public function isIdentifier($fieldName) { if (! $this->identifier) { return false; } if (! $this->isIdentifierComposite) { return $fieldName === $this->identifier[0]; } return in_array($fieldName, $this->identifier, true); } /** * Checks if the field is unique. * * @param string $fieldName The field name. * * @return bool TRUE if the field is unique, FALSE otherwise. */ public function isUniqueField($fieldName) { $mapping = $this->getFieldMapping($fieldName); return $mapping !== false && isset($mapping['unique']) && $mapping['unique']; } /** * Checks if the field is not null. * * @param string $fieldName The field name. * * @return bool TRUE if the field is not null, FALSE otherwise. */ public function isNullable($fieldName) { $mapping = $this->getFieldMapping($fieldName); return $mapping !== false && isset($mapping['nullable']) && $mapping['nullable']; } /** * Gets a column name for a field name. * If the column name for the field cannot be found, the given field name * is returned. * * @param string $fieldName The field name. * * @return string The column name. */ public function getColumnName($fieldName) { return $this->columnNames[$fieldName] ?? $fieldName; } /** * Gets the mapping of a (regular) field that holds some data but not a * reference to another object. * * @param string $fieldName The field name. * * @return mixed[] The field mapping. * @psalm-return FieldMapping * * @throws MappingException */ public function getFieldMapping($fieldName) { if (! isset($this->fieldMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->fieldMappings[$fieldName]; } /** * Gets the mapping of an association. * * @see ClassMetadataInfo::$associationMappings * * @param string $fieldName The field name that represents the association in * the object model. * * @return mixed[] The mapping. * @psalm-return AssociationMapping * * @throws MappingException */ public function getAssociationMapping($fieldName) { if (! isset($this->associationMappings[$fieldName])) { throw MappingException::mappingNotFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]; } /** * Gets all association mappings of the class. * * @psalm-return array<string, AssociationMapping> */ public function getAssociationMappings() { return $this->associationMappings; } /** * Gets the field name for a column name. * If no field name can be found the column name is returned. * * @param string $columnName The column name. * * @return string The column alias. */ public function getFieldName($columnName) { return $this->fieldNames[$columnName] ?? $columnName; } /** * Gets the named query. * * @see ClassMetadataInfo::$namedQueries * * @param string $queryName The query name. * * @return string * * @throws MappingException */ public function getNamedQuery($queryName) { if (! isset($this->namedQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedQueries[$queryName]['dql']; } /** * Gets all named queries of the class. * * @return mixed[][] * @psalm-return array<string, array<string, mixed>> */ public function getNamedQueries() { return $this->namedQueries; } /** * Gets the named native query. * * @see ClassMetadataInfo::$namedNativeQueries * * @param string $queryName The query name. * * @return mixed[] * @psalm-return array<string, mixed> * * @throws MappingException */ public function getNamedNativeQuery($queryName) { if (! isset($this->namedNativeQueries[$queryName])) { throw MappingException::queryNotFound($this->name, $queryName); } return $this->namedNativeQueries[$queryName]; } /** * Gets all named native queries of the class. * * @psalm-return array<string, array<string, mixed>> */ public function getNamedNativeQueries() { return $this->namedNativeQueries; } /** * Gets the result set mapping. * * @see ClassMetadataInfo::$sqlResultSetMappings * * @param string $name The result set mapping name. * * @return mixed[] * @psalm-return array{name: string, entities: array, columns: array} * * @throws MappingException */ public function getSqlResultSetMapping($name) { if (! isset($this->sqlResultSetMappings[$name])) { throw MappingException::resultMappingNotFound($this->name, $name); } return $this->sqlResultSetMappings[$name]; } /** * Gets all sql result set mappings of the class. * * @return mixed[] * @psalm-return array<string, array{name: string, entities: array, columns: array}> */ public function getSqlResultSetMappings() { return $this->sqlResultSetMappings; } /** * Checks whether given property has type * * @param string $name Property name */ private function isTypedProperty(string $name): bool { return PHP_VERSION_ID >= 70400 && isset($this->reflClass) && $this->reflClass->hasProperty($name) && $this->reflClass->getProperty($name)->hasType(); } /** * Validates & completes the given field mapping based on typed property. * * @param array{fieldName: string, type?: string} $mapping The field mapping to validate & complete. * * @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping. */ private function validateAndCompleteTypedFieldMapping(array $mapping): array { $field = $this->reflClass->getProperty($mapping['fieldName']); $mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field); return $mapping; } /** * Validates & completes the basic mapping information based on typed property. * * @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping. * * @return mixed[] The updated mapping. */ private function validateAndCompleteTypedAssociationMapping(array $mapping): array { $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) { return $mapping; } if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) { $mapping['targetEntity'] = $type->getName(); } return $mapping; } /** * Validates & completes the given field mapping. * * @psalm-param array{ * fieldName?: string, * columnName?: string, * id?: bool, * generated?: int, * enumType?: class-string, * } $mapping The field mapping to validate & complete. * * @return FieldMapping The updated mapping. * * @throws MappingException */ protected function validateAndCompleteFieldMapping(array $mapping): array { // Check mandatory fields if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { throw MappingException::missingFieldName($this->name); } if ($this->isTypedProperty($mapping['fieldName'])) { $mapping = $this->validateAndCompleteTypedFieldMapping($mapping); } if (! isset($mapping['type'])) { // Default to string $mapping['type'] = 'string'; } // Complete fieldName and columnName mapping if (! isset($mapping['columnName'])) { $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name); } if ($mapping['columnName'][0] === '`') { $mapping['columnName'] = trim($mapping['columnName'], '`'); $mapping['quoted'] = true; } $this->columnNames[$mapping['fieldName']] = $mapping['columnName']; if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn && $this->discriminatorColumn['name'] === $mapping['columnName'])) { throw MappingException::duplicateColumnName($this->name, $mapping['columnName']); } $this->fieldNames[$mapping['columnName']] = $mapping['fieldName']; // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if ($this->versionField === $mapping['fieldName']) { throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']); } if (! in_array($mapping['fieldName'], $this->identifier, true)) { $this->identifier[] = $mapping['fieldName']; } // Check for composite key if (! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } } if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) { if (isset($mapping['id']) && $mapping['id'] === true) { throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']); } $mapping['requireSQLConversion'] = true; } if (isset($mapping['generated'])) { if (! in_array($mapping['generated'], [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) { throw MappingException::invalidGeneratedMode($mapping['generated']); } if ($mapping['generated'] === self::GENERATED_NEVER) { unset($mapping['generated']); } } if (isset($mapping['enumType'])) { if (PHP_VERSION_ID < 80100) { throw MappingException::enumsRequirePhp81($this->name, $mapping['fieldName']); } if (! enum_exists($mapping['enumType'])) { throw MappingException::nonEnumTypeMapped($this->name, $mapping['fieldName'], $mapping['enumType']); } if (! empty($mapping['id'])) { $this->containsEnumIdentifier = true; } } return $mapping; } /** * Validates & completes the basic mapping information that is common to all * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). * * @psalm-param array<string, mixed> $mapping The mapping. * * @return mixed[] The updated mapping. * @psalm-return AssociationMapping * * @throws MappingException If something is wrong with the mapping. */ protected function _validateAndCompleteAssociationMapping(array $mapping) { if (! isset($mapping['mappedBy'])) { $mapping['mappedBy'] = null; } if (! isset($mapping['inversedBy'])) { $mapping['inversedBy'] = null; } $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy if (empty($mapping['indexBy'])) { unset($mapping['indexBy']); } // If targetEntity is unqualified, assume it is in the same namespace as // the sourceEntity. $mapping['sourceEntity'] = $this->name; if ($this->isTypedProperty($mapping['fieldName'])) { $mapping = $this->validateAndCompleteTypedAssociationMapping($mapping); } if (isset($mapping['targetEntity'])) { $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']); $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); } if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']); } // Complete id mapping if (isset($mapping['id']) && $mapping['id'] === true) { if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) { throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']); } if (! in_array($mapping['fieldName'], $this->identifier, true)) { if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) { throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId( $mapping['targetEntity'], $this->name, $mapping['fieldName'] ); } $this->identifier[] = $mapping['fieldName']; $this->containsForeignIdentifier = true; } // Check for composite key if (! $this->isIdentifierComposite && count($this->identifier) > 1) { $this->isIdentifierComposite = true; } if ($this->cache && ! isset($mapping['cache'])) { throw NonCacheableEntityAssociation::fromEntityAndField( $this->name, $mapping['fieldName'] ); } } // Mandatory attributes for both sides // Mandatory: fieldName, targetEntity if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) { throw MappingException::missingFieldName($this->name); } if (! isset($mapping['targetEntity'])) { throw MappingException::missingTargetEntity($mapping['fieldName']); } // Mandatory and optional attributes for either side if (! $mapping['mappedBy']) { if (isset($mapping['joinTable']) && $mapping['joinTable']) { if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') { $mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`'); $mapping['joinTable']['quoted'] = true; } } } else { $mapping['isOwningSide'] = false; } if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']); } // Fetch mode. Default fetch mode to LAZY, if not set. if (! isset($mapping['fetch'])) { $mapping['fetch'] = self::FETCH_LAZY; } // Cascades $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : []; $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach']; if (in_array('all', $cascades, true)) { $cascades = $allCascades; } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) { throw MappingException::invalidCascadeOption( array_diff($cascades, $allCascades), $this->name, $mapping['fieldName'] ); } $mapping['cascade'] = $cascades; $mapping['isCascadeRemove'] = in_array('remove', $cascades, true); $mapping['isCascadePersist'] = in_array('persist', $cascades, true); $mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true); $mapping['isCascadeMerge'] = in_array('merge', $cascades, true); $mapping['isCascadeDetach'] = in_array('detach', $cascades, true); return $mapping; } /** * Validates & completes a one-to-one association mapping. * * @psalm-param array<string, mixed> $mapping The mapping to validate & complete. * * @return mixed[] The validated & completed mapping. * @psalm-return AssociationMapping * * @throws RuntimeException * @throws MappingException */ protected function _validateAndCompleteOneToOneMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10654', 'JoinColumn configuration is not allowed on the inverse side of one-to-one associations, and will throw a MappingException in Doctrine ORM 3.0' ); } if (isset($mapping['joinColumns']) && $mapping['joinColumns']) { $mapping['isOwningSide'] = true; } if ($mapping['isOwningSide']) { if (empty($mapping['joinColumns'])) { // Apply default join column $mapping['joinColumns'] = [ [ 'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), ], ]; } $uniqueConstraintColumns = []; foreach ($mapping['joinColumns'] as &$joinColumn) { if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) { if (count($mapping['joinColumns']) === 1) { if (empty($mapping['id'])) { $joinColumn['unique'] = true; } } else { $uniqueConstraintColumns[] = $joinColumn['name']; } } if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name']; } if ($uniqueConstraintColumns) { if (! $this->table) { throw new RuntimeException('ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.'); } $this->table['uniqueConstraints'][$mapping['fieldName'] . '_uniq'] = ['columns' => $uniqueConstraintColumns]; } $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']); } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']; $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove']; if ($mapping['orphanRemoval']) { unset($mapping['unique']); } if (isset($mapping['id']) && $mapping['id'] === true && ! $mapping['isOwningSide']) { throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']); } return $mapping; } /** * Validates & completes a one-to-many association mapping. * * @psalm-param array<string, mixed> $mapping The mapping to validate and complete. * * @return mixed[] The validated and completed mapping. * @psalm-return AssociationMapping * * @throws MappingException * @throws InvalidArgumentException */ protected function _validateAndCompleteOneToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); // OneToMany-side MUST be inverse (must have mappedBy) if (! isset($mapping['mappedBy'])) { throw MappingException::oneToManyRequiresMappedBy($this->name, $mapping['fieldName']); } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']; $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove']; $this->assertMappingOrderBy($mapping); return $mapping; } /** * Validates & completes a many-to-many association mapping. * * @psalm-param array<string, mixed> $mapping The mapping to validate & complete. * * @return mixed[] The validated & completed mapping. * @psalm-return AssociationMapping * * @throws InvalidArgumentException */ protected function _validateAndCompleteManyToManyMapping(array $mapping) { $mapping = $this->_validateAndCompleteAssociationMapping($mapping); if ($mapping['isOwningSide']) { // owning side MUST have a join table if (! isset($mapping['joinTable']['name'])) { $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']); } $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] === $mapping['targetEntity'] && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns']))); if (! isset($mapping['joinTable']['joinColumns'])) { $mapping['joinTable']['joinColumns'] = [ [ 'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE', ], ]; } if (! isset($mapping['joinTable']['inverseJoinColumns'])) { $mapping['joinTable']['inverseJoinColumns'] = [ [ 'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null), 'referencedColumnName' => $this->namingStrategy->referenceColumnName(), 'onDelete' => 'CASCADE', ], ]; } $mapping['joinTableColumns'] = []; foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) { if (empty($joinColumn['name'])) { $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']); } if (empty($joinColumn['referencedColumnName'])) { $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($joinColumn['name'][0] === '`') { $joinColumn['name'] = trim($joinColumn['name'], '`'); $joinColumn['quoted'] = true; } if ($joinColumn['referencedColumnName'][0] === '`') { $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`'); $joinColumn['quoted'] = true; } if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) === 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $joinColumn['name']; } foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) { if (empty($inverseJoinColumn['name'])) { $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']); } if (empty($inverseJoinColumn['referencedColumnName'])) { $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName(); } if ($inverseJoinColumn['name'][0] === '`') { $inverseJoinColumn['name'] = trim($inverseJoinColumn['name'], '`'); $inverseJoinColumn['quoted'] = true; } if ($inverseJoinColumn['referencedColumnName'][0] === '`') { $inverseJoinColumn['referencedColumnName'] = trim($inverseJoinColumn['referencedColumnName'], '`'); $inverseJoinColumn['quoted'] = true; } if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) === 'cascade') { $mapping['isOnDeleteCascade'] = true; } $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName']; $mapping['joinTableColumns'][] = $inverseJoinColumn['name']; } } $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']; $this->assertMappingOrderBy($mapping); return $mapping; } /** * {@inheritDoc} */ public function getIdentifierFieldNames() { return $this->identifier; } /** * Gets the name of the single id field. Note that this only works on * entity classes that have a single-field pk. * * @return string * * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. */ public function getSingleIdentifierFieldName() { if ($this->isIdentifierComposite) { throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name); } if (! isset($this->identifier[0])) { throw MappingException::noIdDefined($this->name); } return $this->identifier[0]; } /** * Gets the column name of the single id column. Note that this only works on * entity classes that have a single-field pk. * * @return string * * @throws MappingException If the class doesn't have an identifier or it has a composite primary key. */ public function getSingleIdentifierColumnName() { return $this->getColumnName($this->getSingleIdentifierFieldName()); } /** * INTERNAL: * Sets the mapped identifier/primary key fields of this class. * Mainly used by the ClassMetadataFactory to assign inherited identifiers. * * @psalm-param list<mixed> $identifier * * @return void */ public function setIdentifier(array $identifier) { $this->identifier = $identifier; $this->isIdentifierComposite = (count($this->identifier) > 1); } /** * {@inheritDoc} */ public function getIdentifier() { return $this->identifier; } /** * {@inheritDoc} */ public function hasField($fieldName) { return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]); } /** * Gets an array containing all the column names. * * @psalm-param list<string>|null $fieldNames * * @return mixed[] * @psalm-return list<string> */ public function getColumnNames(?array $fieldNames = null) { if ($fieldNames === null) { return array_keys($this->fieldNames); } return array_values(array_map([$this, 'getColumnName'], $fieldNames)); } /** * Returns an array with all the identifier column names. * * @psalm-return list<string> */ public function getIdentifierColumnNames() { $columnNames = []; foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $columnNames[] = $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocColumnNames = array_map(static function ($joinColumn) { return $joinColumn['name']; }, $joinColumns); $columnNames = array_merge($columnNames, $assocColumnNames); } return $columnNames; } /** * Sets the type of Id generator to use for the mapped class. * * @param int $generatorType * @psalm-param self::GENERATOR_TYPE_* $generatorType * * @return void */ public function setIdGeneratorType($generatorType) { $this->generatorType = $generatorType; } /** * Checks whether the mapped class uses an Id generator. * * @return bool TRUE if the mapped class uses an Id generator, FALSE otherwise. */ public function usesIdGenerator() { return $this->generatorType !== self::GENERATOR_TYPE_NONE; } /** @return bool */ public function isInheritanceTypeNone() { return $this->inheritanceType === self::INHERITANCE_TYPE_NONE; } /** * Checks whether the mapped class uses the JOINED inheritance mapping strategy. * * @return bool TRUE if the class participates in a JOINED inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeJoined() { return $this->inheritanceType === self::INHERITANCE_TYPE_JOINED; } /** * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy. * * @return bool TRUE if the class participates in a SINGLE_TABLE inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeSingleTable() { return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_TABLE; } /** * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy. * * @deprecated * * @return bool TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping, * FALSE otherwise. */ public function isInheritanceTypeTablePerClass() { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10414/', 'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement' ); return $this->inheritanceType === self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Checks whether the class uses an identity column for the Id generation. * * @return bool TRUE if the class uses the IDENTITY generator, FALSE otherwise. */ public function isIdGeneratorIdentity() { return $this->generatorType === self::GENERATOR_TYPE_IDENTITY; } /** * Checks whether the class uses a sequence for id generation. * * @return bool TRUE if the class uses the SEQUENCE generator, FALSE otherwise. * * @psalm-assert-if-true !null $this->sequenceGeneratorDefinition */ public function isIdGeneratorSequence() { return $this->generatorType === self::GENERATOR_TYPE_SEQUENCE; } /** * Checks whether the class uses a table for id generation. * * @deprecated * * @return false */ public function isIdGeneratorTable() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9046', '%s is deprecated', __METHOD__ ); return false; } /** * Checks whether the class has a natural identifier/pk (which means it does * not use any Id generator. * * @return bool */ public function isIdentifierNatural() { return $this->generatorType === self::GENERATOR_TYPE_NONE; } /** * Checks whether the class use a UUID for id generation. * * @deprecated * * @return bool */ public function isIdentifierUuid() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9046', '%s is deprecated', __METHOD__ ); return $this->generatorType === self::GENERATOR_TYPE_UUID; } /** * Gets the type of a field. * * @param string $fieldName * * @return string|null * * @todo 3.0 Remove this. PersisterHelper should fix it somehow */ public function getTypeOfField($fieldName) { return isset($this->fieldMappings[$fieldName]) ? $this->fieldMappings[$fieldName]['type'] : null; } /** * Gets the type of a column. * * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column * that is derived by a referenced field on a different entity. * * @param string $columnName * * @return string|null */ public function getTypeOfColumn($columnName) { return $this->getTypeOfField($this->getFieldName($columnName)); } /** * Gets the name of the primary table. * * @return string */ public function getTableName() { return $this->table['name']; } /** * Gets primary table's schema name. * * @return string|null */ public function getSchemaName() { return $this->table['schema'] ?? null; } /** * Gets the table name to use for temporary identifier tables of this class. * * @return string */ public function getTemporaryIdTableName() { // replace dots with underscores because PostgreSQL creates temporary tables in a special schema return str_replace('.', '_', $this->getTableName() . '_id_tmp'); } /** * Sets the mapped subclasses of this class. * * @psalm-param list<string> $subclasses The names of all mapped subclasses. * * @return void */ public function setSubclasses(array $subclasses) { foreach ($subclasses as $subclass) { $this->subClasses[] = $this->fullyQualifiedClassName($subclass); } } /** * Sets the parent class names. Only <em>entity</em> classes may be given. * * Assumes that the class names in the passed array are in the order: * directParent -> directParentParent -> directParentParentParent ... -> root. * * @psalm-param list<class-string> $classNames * * @return void */ public function setParentClasses(array $classNames) { $this->parentClasses = $classNames; if (count($classNames) > 0) { $this->rootEntityName = array_pop($classNames); } } /** * Sets the inheritance type used by the class and its subclasses. * * @param int $type * @psalm-param self::INHERITANCE_TYPE_* $type * * @return void * * @throws MappingException */ public function setInheritanceType($type) { if (! $this->isInheritanceType($type)) { throw MappingException::invalidInheritanceType($this->name, $type); } $this->inheritanceType = $type; } /** * Sets the association to override association mapping of property for an entity relationship. * * @param string $fieldName * @psalm-param array<string, mixed> $overrideMapping * * @return void * * @throws MappingException */ public function setAssociationOverride($fieldName, array $overrideMapping) { if (! isset($this->associationMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->associationMappings[$fieldName]; if (isset($mapping['inherited'])) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10470', 'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s. This is a misconfiguration and will be an error in Doctrine ORM 3.0.', $this->name, $fieldName, $mapping['inherited'] ); } if (isset($overrideMapping['joinColumns'])) { $mapping['joinColumns'] = $overrideMapping['joinColumns']; } if (isset($overrideMapping['inversedBy'])) { $mapping['inversedBy'] = $overrideMapping['inversedBy']; } if (isset($overrideMapping['joinTable'])) { $mapping['joinTable'] = $overrideMapping['joinTable']; } if (isset($overrideMapping['fetch'])) { $mapping['fetch'] = $overrideMapping['fetch']; } $mapping['joinColumnFieldNames'] = null; $mapping['joinTableColumns'] = null; $mapping['sourceToTargetKeyColumns'] = null; $mapping['relationToSourceKeyColumns'] = null; $mapping['relationToTargetKeyColumns'] = null; switch ($mapping['type']) { case self::ONE_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::ONE_TO_MANY: $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); break; case self::MANY_TO_ONE: $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); break; case self::MANY_TO_MANY: $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); break; } $this->associationMappings[$fieldName] = $mapping; } /** * Sets the override for a mapped field. * * @param string $fieldName * @psalm-param array<string, mixed> $overrideMapping * * @return void * * @throws MappingException */ public function setAttributeOverride($fieldName, array $overrideMapping) { if (! isset($this->fieldMappings[$fieldName])) { throw MappingException::invalidOverrideFieldName($this->name, $fieldName); } $mapping = $this->fieldMappings[$fieldName]; if (isset($mapping['inherited'])) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10470', 'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s. This is a misconfiguration and will be an error in Doctrine ORM 3.0.', $this->name, $fieldName, $mapping['inherited'] ); //throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName); } if (isset($mapping['id'])) { $overrideMapping['id'] = $mapping['id']; } if (! isset($overrideMapping['type'])) { $overrideMapping['type'] = $mapping['type']; } if (! isset($overrideMapping['fieldName'])) { $overrideMapping['fieldName'] = $mapping['fieldName']; } if ($overrideMapping['type'] !== $mapping['type']) { throw MappingException::invalidOverrideFieldType($this->name, $fieldName); } unset($this->fieldMappings[$fieldName]); unset($this->fieldNames[$mapping['columnName']]); unset($this->columnNames[$mapping['fieldName']]); $overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping); $this->fieldMappings[$fieldName] = $overrideMapping; } /** * Checks whether a mapped field is inherited from an entity superclass. * * @param string $fieldName * * @return bool TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedField($fieldName) { return isset($this->fieldMappings[$fieldName]['inherited']); } /** * Checks if this entity is the root in any entity-inheritance-hierarchy. * * @return bool */ public function isRootEntity() { return $this->name === $this->rootEntityName; } /** * Checks whether a mapped association field is inherited from a superclass. * * @param string $fieldName * * @return bool TRUE if the field is inherited, FALSE otherwise. */ public function isInheritedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]['inherited']); } /** * @param string $fieldName * * @return bool */ public function isInheritedEmbeddedClass($fieldName) { return isset($this->embeddedClasses[$fieldName]['inherited']); } /** * Sets the name of the primary table the class is mapped to. * * @deprecated Use {@link setPrimaryTable}. * * @param string $tableName The table name. * * @return void */ public function setTableName($tableName) { $this->table['name'] = $tableName; } /** * Sets the primary table definition. The provided array supports the * following structure: * * name => <tableName> (optional, defaults to class name) * indexes => array of indexes (optional) * uniqueConstraints => array of constraints (optional) * * If a key is omitted, the current value is kept. * * @psalm-param array<string, mixed> $table The table description. * * @return void */ public function setPrimaryTable(array $table) { if (isset($table['name'])) { // Split schema and table name from a table name like "myschema.mytable" if (str_contains($table['name'], '.')) { [$this->table['schema'], $table['name']] = explode('.', $table['name'], 2); } if ($table['name'][0] === '`') { $table['name'] = trim($table['name'], '`'); $this->table['quoted'] = true; } $this->table['name'] = $table['name']; } if (isset($table['quoted'])) { $this->table['quoted'] = $table['quoted']; } if (isset($table['schema'])) { $this->table['schema'] = $table['schema']; } if (isset($table['indexes'])) { $this->table['indexes'] = $table['indexes']; } if (isset($table['uniqueConstraints'])) { $this->table['uniqueConstraints'] = $table['uniqueConstraints']; } if (isset($table['options'])) { $this->table['options'] = $table['options']; } } /** * Checks whether the given type identifies an inheritance type. * * @return bool TRUE if the given type identifies an inheritance type, FALSE otherwise. */ private function isInheritanceType(int $type): bool { if ($type === self::INHERITANCE_TYPE_TABLE_PER_CLASS) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10414/', 'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement' ); } return $type === self::INHERITANCE_TYPE_NONE || $type === self::INHERITANCE_TYPE_SINGLE_TABLE || $type === self::INHERITANCE_TYPE_JOINED || $type === self::INHERITANCE_TYPE_TABLE_PER_CLASS; } /** * Adds a mapped field to the class. * * @psalm-param array<string, mixed> $mapping The field mapping. * * @return void * * @throws MappingException */ public function mapField(array $mapping) { $mapping = $this->validateAndCompleteFieldMapping($mapping); $this->assertFieldNotMapped($mapping['fieldName']); if (isset($mapping['generated'])) { $this->requiresFetchAfterChange = true; } $this->fieldMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds an association mapping without completing/validating it. * This is mainly used to add inherited association mappings to derived classes. * * @psalm-param AssociationMapping $mapping * * @return void * * @throws MappingException */ public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/) { if (isset($this->associationMappings[$mapping['fieldName']])) { throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']); } $this->associationMappings[$mapping['fieldName']] = $mapping; } /** * INTERNAL: * Adds a field mapping without completing/validating it. * This is mainly used to add inherited field mappings to derived classes. * * @psalm-param FieldMapping $fieldMapping * * @return void */ public function addInheritedFieldMapping(array $fieldMapping) { $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping; $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName']; $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName']; } /** * INTERNAL: * Adds a named query to this class. * * @deprecated * * @psalm-param array<string, mixed> $queryMapping * * @return void * * @throws MappingException */ public function addNamedQuery(array $queryMapping) { if (! isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8592', 'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository', $queryMapping['name'], $this->name ); if (isset($this->namedQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (! isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } $name = $queryMapping['name']; $query = $queryMapping['query']; $dql = str_replace('__CLASS__', $this->name, $query); $this->namedQueries[$name] = [ 'name' => $name, 'query' => $query, 'dql' => $dql, ]; } /** * INTERNAL: * Adds a named native query to this class. * * @deprecated * * @psalm-param array<string, mixed> $queryMapping * * @return void * * @throws MappingException */ public function addNamedNativeQuery(array $queryMapping) { if (! isset($queryMapping['name'])) { throw MappingException::nameIsMandatoryForQueryMapping($this->name); } Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8592', 'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository', $queryMapping['name'], $this->name ); if (isset($this->namedNativeQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } if (! isset($queryMapping['query'])) { throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); } if (! isset($queryMapping['resultClass']) && ! isset($queryMapping['resultSetMapping'])) { throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } $queryMapping['isSelfClass'] = false; if (isset($queryMapping['resultClass'])) { if ($queryMapping['resultClass'] === '__CLASS__') { $queryMapping['isSelfClass'] = true; $queryMapping['resultClass'] = $this->name; } $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']); $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; } /** * INTERNAL: * Adds a sql result set mapping to this class. * * @psalm-param array<string, mixed> $resultMapping * * @return void * * @throws MappingException */ public function addSqlResultSetMapping(array $resultMapping) { if (! isset($resultMapping['name'])) { throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name); } if (isset($this->sqlResultSetMappings[$resultMapping['name']])) { throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']); } if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityResult) { if (! isset($entityResult['entityClass'])) { throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } $entityResult['isSelfClass'] = false; if ($entityResult['entityClass'] === '__CLASS__') { $entityResult['isSelfClass'] = true; $entityResult['entityClass'] = $this->name; } $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']); $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass']; if (isset($entityResult['fields'])) { foreach ($entityResult['fields'] as $k => $field) { if (! isset($field['name'])) { throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']); } if (! isset($field['column'])) { $fieldName = $field['name']; if (str_contains($fieldName, '.')) { [, $fieldName] = explode('.', $fieldName); } $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName; } } } } } $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping; } /** * Adds a one-to-one mapping. * * @param array<string, mixed> $mapping The mapping. * * @return void */ public function mapOneToOne(array $mapping) { $mapping['type'] = self::ONE_TO_ONE; $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a one-to-many mapping. * * @psalm-param array<string, mixed> $mapping The mapping. * * @return void */ public function mapOneToMany(array $mapping) { $mapping['type'] = self::ONE_TO_MANY; $mapping = $this->_validateAndCompleteOneToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-one mapping. * * @psalm-param array<string, mixed> $mapping The mapping. * * @return void */ public function mapManyToOne(array $mapping) { $mapping['type'] = self::MANY_TO_ONE; // A many-to-one mapping is essentially a one-one backreference $mapping = $this->_validateAndCompleteOneToOneMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Adds a many-to-many mapping. * * @psalm-param array<string, mixed> $mapping The mapping. * * @return void */ public function mapManyToMany(array $mapping) { $mapping['type'] = self::MANY_TO_MANY; $mapping = $this->_validateAndCompleteManyToManyMapping($mapping); $this->_storeAssociationMapping($mapping); } /** * Stores the association mapping. * * @psalm-param AssociationMapping $assocMapping * * @return void * * @throws MappingException */ protected function _storeAssociationMapping(array $assocMapping) { $sourceFieldName = $assocMapping['fieldName']; $this->assertFieldNotMapped($sourceFieldName); $this->associationMappings[$sourceFieldName] = $assocMapping; } /** * Registers a custom repository class for the entity class. * * @param string|null $repositoryClassName The class name of the custom mapper. * @psalm-param class-string<EntityRepository>|null $repositoryClassName * * @return void */ public function setCustomRepositoryClass($repositoryClassName) { $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName); } /** * Dispatches the lifecycle event of the given entity to the registered * lifecycle callbacks and lifecycle listeners. * * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker * * @param string $lifecycleEvent The lifecycle event. * @param object $entity The Entity on which the event occurred. * * @return void */ public function invokeLifecycleCallbacks($lifecycleEvent, $entity) { foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { $entity->$callback(); } } /** * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event. * * @param string $lifecycleEvent * * @return bool */ public function hasLifecycleCallbacks($lifecycleEvent) { return isset($this->lifecycleCallbacks[$lifecycleEvent]); } /** * Gets the registered lifecycle callbacks for an event. * * @param string $event * * @return string[] * @psalm-return list<string> */ public function getLifecycleCallbacks($event) { return $this->lifecycleCallbacks[$event] ?? []; } /** * Adds a lifecycle callback for entities of this class. * * @param string $callback * @param string $event * * @return void */ public function addLifecycleCallback($callback, $event) { if ($this->isEmbeddedClass) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/8381', 'Registering lifecycle callback %s on Embedded class %s is not doing anything and will throw exception in 3.0', $event, $this->name ); } if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event], true)) { return; } $this->lifecycleCallbacks[$event][] = $callback; } /** * Sets the lifecycle callbacks for entities of this class. * Any previously registered callbacks are overwritten. * * @psalm-param array<string, list<string>> $callbacks * * @return void */ public function setLifecycleCallbacks(array $callbacks) { $this->lifecycleCallbacks = $callbacks; } /** * Adds a entity listener for entities of this class. * * @param string $eventName The entity lifecycle event. * @param string $class The listener class. * @param string $method The listener callback method. * * @return void * * @throws MappingException */ public function addEntityListener($eventName, $class, $method) { $class = $this->fullyQualifiedClassName($class); $listener = [ 'class' => $class, 'method' => $method, ]; if (! class_exists($class)) { throw MappingException::entityListenerClassNotFound($class, $this->name); } if (! method_exists($class, $method)) { throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); } if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName], true)) { throw MappingException::duplicateEntityListener($class, $method, $this->name); } $this->entityListeners[$eventName][] = $listener; } /** * Sets the discriminator column definition. * * @see getDiscriminatorColumn() * * @param mixed[]|null $columnDef * @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null, options?: array<string, mixed>}|null $columnDef * * @return void * * @throws MappingException */ public function setDiscriminatorColumn($columnDef) { if ($columnDef !== null) { if (! isset($columnDef['name'])) { throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); } if (isset($this->fieldNames[$columnDef['name']])) { throw MappingException::duplicateColumnName($this->name, $columnDef['name']); } if (! isset($columnDef['fieldName'])) { $columnDef['fieldName'] = $columnDef['name']; } if (! isset($columnDef['type'])) { $columnDef['type'] = 'string'; } if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) { throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); } $this->discriminatorColumn = $columnDef; } } /** * @return array<string, mixed> * @psalm-return DiscriminatorColumnMapping */ final public function getDiscriminatorColumn(): array { if ($this->discriminatorColumn === null) { throw new LogicException('The discriminator column was not set.'); } return $this->discriminatorColumn; } /** * Sets the discriminator values used by this class. * Used for JOINED and SINGLE_TABLE inheritance mapping strategies. * * @param array<int|string, string> $map * * @return void */ public function setDiscriminatorMap(array $map) { foreach ($map as $value => $className) { $this->addDiscriminatorMapClass($value, $className); } } /** * Adds one entry of the discriminator map with a new class and corresponding name. * * @param int|string $name * @param string $className * * @return void * * @throws MappingException */ public function addDiscriminatorMapClass($name, $className) { $className = $this->fullyQualifiedClassName($className); $className = ltrim($className, '\\'); $this->discriminatorMap[$name] = $className; if ($this->name === $className) { $this->discriminatorValue = $name; return; } if (! (class_exists($className) || interface_exists($className))) { throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); } $this->addSubClass($className); } /** @param array<class-string> $classes */ public function addSubClasses(array $classes): void { foreach ($classes as $className) { $this->addSubClass($className); } } public function addSubClass(string $className): void { // By ignoring classes that are not subclasses of the current class, we simplify inheriting // the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata. if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) { $this->subClasses[] = $className; } } /** * Checks whether the class has a named query with the given query name. * * @param string $queryName * * @return bool */ public function hasNamedQuery($queryName) { return isset($this->namedQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $queryName * * @return bool */ public function hasNamedNativeQuery($queryName) { return isset($this->namedNativeQueries[$queryName]); } /** * Checks whether the class has a named native query with the given query name. * * @param string $name * * @return bool */ public function hasSqlResultSetMapping($name) { return isset($this->sqlResultSetMappings[$name]); } /** * {@inheritDoc} */ public function hasAssociation($fieldName) { return isset($this->associationMappings[$fieldName]); } /** * {@inheritDoc} */ public function isSingleValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * {@inheritDoc} */ public function isCollectionValuedAssociation($fieldName) { return isset($this->associationMappings[$fieldName]) && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE); } /** * Is this an association that only has a single join column? * * @param string $fieldName * * @return bool */ public function isAssociationWithSingleJoinColumn($fieldName) { return isset($this->associationMappings[$fieldName]) && isset($this->associationMappings[$fieldName]['joinColumns'][0]) && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]); } /** * Returns the single association join column (if any). * * @param string $fieldName * * @return string * * @throws MappingException */ public function getSingleAssociationJoinColumnName($fieldName) { if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['name']; } /** * Returns the single association referenced join column name (if any). * * @param string $fieldName * * @return string * * @throws MappingException */ public function getSingleAssociationReferencedJoinColumnName($fieldName) { if (! $this->isAssociationWithSingleJoinColumn($fieldName)) { throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName); } return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName']; } /** * Used to retrieve a fieldname for either field or association from a given column. * * This method is used in foreign-key as primary-key contexts. * * @param string $columnName * * @return string * * @throws MappingException */ public function getFieldForColumn($columnName) { if (isset($this->fieldNames[$columnName])) { return $this->fieldNames[$columnName]; } foreach ($this->associationMappings as $assocName => $mapping) { if ( $this->isAssociationWithSingleJoinColumn($assocName) && $this->associationMappings[$assocName]['joinColumns'][0]['name'] === $columnName ) { return $assocName; } } throw MappingException::noFieldNameFoundForColumn($this->name, $columnName); } /** * Sets the ID generator used to generate IDs for instances of this class. * * @param AbstractIdGenerator $generator * * @return void */ public function setIdGenerator($generator) { $this->idGenerator = $generator; } /** * Sets definition. * * @psalm-param array<string, string|null> $definition * * @return void */ public function setCustomGeneratorDefinition(array $definition) { $this->customGeneratorDefinition = $definition; } /** * Sets the definition of the sequence ID generator for this class. * * The definition must have the following structure: * <code> * array( * 'sequenceName' => 'name', * 'allocationSize' => 20, * 'initialValue' => 1 * 'quoted' => 1 * ) * </code> * * @psalm-param array{sequenceName?: string, allocationSize?: int|string, initialValue?: int|string, quoted?: mixed} $definition * * @return void * * @throws MappingException */ public function setSequenceGeneratorDefinition(array $definition) { if (! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') { throw MappingException::missingSequenceName($this->name); } if ($definition['sequenceName'][0] === '`') { $definition['sequenceName'] = trim($definition['sequenceName'], '`'); $definition['quoted'] = true; } if (! isset($definition['allocationSize']) || trim((string) $definition['allocationSize']) === '') { $definition['allocationSize'] = '1'; } if (! isset($definition['initialValue']) || trim((string) $definition['initialValue']) === '') { $definition['initialValue'] = '1'; } $definition['allocationSize'] = (string) $definition['allocationSize']; $definition['initialValue'] = (string) $definition['initialValue']; $this->sequenceGeneratorDefinition = $definition; } /** * Sets the version field mapping used for versioning. Sets the default * value to use depending on the column type. * * @psalm-param array<string, mixed> $mapping The version field mapping array. * * @return void * * @throws MappingException */ public function setVersionMapping(array &$mapping) { $this->isVersioned = true; $this->versionField = $mapping['fieldName']; $this->requiresFetchAfterChange = true; if (! isset($mapping['default'])) { if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) { $mapping['default'] = 1; } elseif ($mapping['type'] === 'datetime') { $mapping['default'] = 'CURRENT_TIMESTAMP'; } else { throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']); } } } /** * Sets whether this class is to be versioned for optimistic locking. * * @param bool $bool * * @return void */ public function setVersioned($bool) { $this->isVersioned = $bool; if ($bool) { $this->requiresFetchAfterChange = true; } } /** * Sets the name of the field that is to be used for versioning if this class is * versioned for optimistic locking. * * @param string|null $versionField * * @return void */ public function setVersionField($versionField) { $this->versionField = $versionField; } /** * Marks this class as read only, no change tracking is applied to it. * * @return void */ public function markReadOnly() { $this->isReadOnly = true; } /** * {@inheritDoc} */ public function getFieldNames() { return array_keys($this->fieldMappings); } /** * {@inheritDoc} */ public function getAssociationNames() { return array_keys($this->associationMappings); } /** * {@inheritDoc} * * @param string $assocName * * @return string * @psalm-return class-string * * @throws InvalidArgumentException */ public function getAssociationTargetClass($assocName) { if (! isset($this->associationMappings[$assocName])) { throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association."); } return $this->associationMappings[$assocName]['targetEntity']; } /** * {@inheritDoc} */ public function getName() { return $this->name; } /** * Gets the (possibly quoted) identifier column names for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param AbstractPlatform $platform * * @return string[] * @psalm-return list<string> */ public function getQuotedIdentifierColumnNames($platform) { $quotedColumnNames = []; foreach ($this->identifier as $idProperty) { if (isset($this->fieldMappings[$idProperty])) { $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName']) : $this->fieldMappings[$idProperty]['columnName']; continue; } // Association defined as Id field $joinColumns = $this->associationMappings[$idProperty]['joinColumns']; $assocQuotedColumnNames = array_map( static function ($joinColumn) use ($platform) { return isset($joinColumn['quoted']) ? $platform->quoteIdentifier($joinColumn['name']) : $joinColumn['name']; }, $joinColumns ); $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames); } return $quotedColumnNames; } /** * Gets the (possibly quoted) column name of a mapped field for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param string $field * @param AbstractPlatform $platform * * @return string */ public function getQuotedColumnName($field, $platform) { return isset($this->fieldMappings[$field]['quoted']) ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) : $this->fieldMappings[$field]['columnName']; } /** * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param AbstractPlatform $platform * * @return string */ public function getQuotedTableName($platform) { return isset($this->table['quoted']) ? $platform->quoteIdentifier($this->table['name']) : $this->table['name']; } /** * Gets the (possibly quoted) name of the join table. * * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy * * @param mixed[] $assoc * @param AbstractPlatform $platform * * @return string */ public function getQuotedJoinTableName(array $assoc, $platform) { return isset($assoc['joinTable']['quoted']) ? $platform->quoteIdentifier($assoc['joinTable']['name']) : $assoc['joinTable']['name']; } /** * {@inheritDoc} */ public function isAssociationInverseSide($fieldName) { return isset($this->associationMappings[$fieldName]) && ! $this->associationMappings[$fieldName]['isOwningSide']; } /** * {@inheritDoc} */ public function getAssociationMappedByTargetField($fieldName) { return $this->associationMappings[$fieldName]['mappedBy']; } /** * @param string $targetClass * * @return mixed[][] * @psalm-return array<string, array<string, mixed>> */ public function getAssociationsByTargetClass($targetClass) { $relations = []; foreach ($this->associationMappings as $mapping) { if ($mapping['targetEntity'] === $targetClass) { $relations[$mapping['fieldName']] = $mapping; } } return $relations; } /** * @param string|null $className * * @return string|null null if the input value is null * @psalm-return class-string|null */ public function fullyQualifiedClassName($className) { if (empty($className)) { return $className; } if (! str_contains($className, '\\') && $this->namespace) { return $this->namespace . '\\' . $className; } return $className; } /** * @param string $name * * @return mixed */ public function getMetadataValue($name) { if (isset($this->$name)) { return $this->$name; } return null; } /** * Map Embedded Class * * @psalm-param array<string, mixed> $mapping * * @return void * * @throws MappingException */ public function mapEmbedded(array $mapping) { $this->assertFieldNotMapped($mapping['fieldName']); if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) { $type = $this->reflClass->getProperty($mapping['fieldName'])->getType(); if ($type instanceof ReflectionNamedType) { $mapping['class'] = $type->getName(); } } if (! (isset($mapping['class']) && $mapping['class'])) { throw MappingException::missingEmbeddedClass($mapping['fieldName']); } $fqcn = $this->fullyQualifiedClassName($mapping['class']); assert($fqcn !== null); $this->embeddedClasses[$mapping['fieldName']] = [ 'class' => $fqcn, 'columnPrefix' => $mapping['columnPrefix'] ?? null, 'declaredField' => $mapping['declaredField'] ?? null, 'originalField' => $mapping['originalField'] ?? null, ]; } /** * Inline the embeddable class * * @param string $property * * @return void */ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) { foreach ($embeddable->fieldMappings as $fieldMapping) { $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name; $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) ? $property . '.' . $fieldMapping['declaredField'] : $property; $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName']; if (! empty($this->embeddedClasses[$property]['columnPrefix'])) { $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName']; } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) { $fieldMapping['columnName'] = $this->namingStrategy ->embeddedFieldToColumnName( $property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name ); } $this->mapField($fieldMapping); } } /** @throws MappingException */ private function assertFieldNotMapped(string $fieldName): void { if ( isset($this->fieldMappings[$fieldName]) || isset($this->associationMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]) ) { throw MappingException::duplicateFieldMapping($this->name, $fieldName); } } /** * Gets the sequence name based on class metadata. * * @return string * * @todo Sequence names should be computed in DBAL depending on the platform */ public function getSequenceName(AbstractPlatform $platform) { $sequencePrefix = $this->getSequencePrefix($platform); $columnName = $this->getSingleIdentifierColumnName(); return $sequencePrefix . '_' . $columnName . '_seq'; } /** * Gets the sequence name prefix based on class metadata. * * @return string * * @todo Sequence names should be computed in DBAL depending on the platform */ public function getSequencePrefix(AbstractPlatform $platform) { $tableName = $this->getTableName(); $sequencePrefix = $tableName; // Prepend the schema name to the table name if there is one $schemaName = $this->getSchemaName(); if ($schemaName) { $sequencePrefix = $schemaName . '.' . $tableName; if (! $platform->supportsSchemas() && $platform->canEmulateSchemas()) { $sequencePrefix = $schemaName . '__' . $tableName; } } return $sequencePrefix; } /** @psalm-param AssociationMapping $mapping */ private function assertMappingOrderBy(array $mapping): void { if (isset($mapping['orderBy']) && ! is_array($mapping['orderBy'])) { throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy'])); } } /** @psalm-param class-string $class */ private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ?ReflectionProperty { $reflectionProperty = $reflService->getAccessibleProperty($class, $field); if ($reflectionProperty !== null && PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) { $declaringClass = $reflectionProperty->getDeclaringClass()->name; if ($declaringClass !== $class) { $reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field); } if ($reflectionProperty !== null) { $reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty); } } return $reflectionProperty; } } orm/lib/Doctrine/ORM/Mapping/Column.php 0000644 00000005571 15120025734 0013663 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use BackedEnum; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor * @Target({"PROPERTY","ANNOTATION"}) */ #[Attribute(Attribute::TARGET_PROPERTY)] final class Column implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var mixed * @readonly */ public $type; /** * @var int|null * @readonly */ public $length; /** * The precision for a decimal (exact numeric) column (Applies only for decimal column). * * @var int|null * @readonly */ public $precision = 0; /** * The scale for a decimal (exact numeric) column (Applies only for decimal column). * * @var int|null * @readonly */ public $scale = 0; /** * @var bool * @readonly */ public $unique = false; /** * @var bool * @readonly */ public $nullable = false; /** * @var bool * @readonly */ public $insertable = true; /** * @var bool * @readonly */ public $updatable = true; /** * @var class-string<BackedEnum>|null * @readonly */ public $enumType = null; /** * @var array<string,mixed> * @readonly */ public $options = []; /** * @var string|null * @readonly */ public $columnDefinition; /** * @var string|null * @readonly * @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null * @Enum({"NEVER", "INSERT", "ALWAYS"}) */ public $generated; /** * @param class-string<BackedEnum>|null $enumType * @param array<string,mixed> $options * @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated */ public function __construct( ?string $name = null, ?string $type = null, ?int $length = null, ?int $precision = null, ?int $scale = null, bool $unique = false, bool $nullable = false, bool $insertable = true, bool $updatable = true, ?string $enumType = null, array $options = [], ?string $columnDefinition = null, ?string $generated = null ) { $this->name = $name; $this->type = $type; $this->length = $length; $this->precision = $precision; $this->scale = $scale; $this->unique = $unique; $this->nullable = $nullable; $this->insertable = $insertable; $this->updatable = $updatable; $this->enumType = $enumType; $this->options = $options; $this->columnDefinition = $columnDefinition; $this->generated = $generated; } } orm/lib/Doctrine/ORM/Mapping/ColumnResult.php 0000644 00000000731 15120025734 0015053 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * References name of a column in the SELECT clause of a SQL query. * Scalar result types can be included in the query result by specifying this annotation in the metadata. * * @Annotation * @Target("ANNOTATION") */ final class ColumnResult implements MappingAttribute { /** * The name of a column in the SELECT clause of a SQL query. * * @var string */ public $name; } orm/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php 0000644 00000000770 15120025734 0016020 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor * @Target("PROPERTY") */ #[Attribute(Attribute::TARGET_PROPERTY)] final class CustomIdGenerator implements MappingAttribute { /** * @var string|null * @readonly */ public $class; public function __construct(?string $class = null) { $this->class = $class; } } orm/lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php 0000644 00000002610 15120025734 0020426 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use InvalidArgumentException; use function get_class; use function gettype; use function is_object; use function sprintf; use function trim; /** * The default DefaultEntityListener */ class DefaultEntityListenerResolver implements EntityListenerResolver { /** @psalm-var array<class-string, object> Map to store entity listener instances. */ private $instances = []; /** * {@inheritDoc} */ public function clear($className = null) { if ($className === null) { $this->instances = []; return; } $className = trim($className, '\\'); if (isset($this->instances[$className])) { unset($this->instances[$className]); } } /** * {@inheritDoc} */ public function register($object) { if (! is_object($object)) { throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object))); } $this->instances[get_class($object)] = $object; } /** * {@inheritDoc} */ public function resolve($className) { $className = trim($className, '\\'); if (isset($this->instances[$className])) { return $this->instances[$className]; } return $this->instances[$className] = new $className(); } } orm/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php 0000644 00000003504 15120025734 0016661 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use function str_contains; use function strrpos; use function strtolower; use function substr; /** * The default NamingStrategy * * @link www.doctrine-project.org */ class DefaultNamingStrategy implements NamingStrategy { /** * {@inheritDoc} */ public function classToTableName($className) { if (str_contains($className, '\\')) { return substr($className, strrpos($className, '\\') + 1); } return $className; } /** * {@inheritDoc} */ public function propertyToColumnName($propertyName, $className = null) { return $propertyName; } /** * {@inheritDoc} */ public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null) { return $propertyName . '_' . $embeddedColumnName; } /** * {@inheritDoc} */ public function referenceColumnName() { return 'id'; } /** * {@inheritDoc} * * @param string $propertyName * @param class-string $className */ public function joinColumnName($propertyName, $className = null) { return $propertyName . '_' . $this->referenceColumnName(); } /** * {@inheritDoc} */ public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) { return strtolower($this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity)); } /** * {@inheritDoc} */ public function joinKeyColumnName($entityName, $referencedColumnName = null) { return strtolower($this->classToTableName($entityName) . '_' . ($referencedColumnName ?: $this->referenceColumnName())); } } orm/lib/Doctrine/ORM/Mapping/Embeddable.php 0000644 00000000332 15120025734 0014420 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class Embeddable implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/Entity.php 0000644 00000001553 15120025735 0013677 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\ORM\EntityRepository; /** * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") * @template T of object */ #[Attribute(Attribute::TARGET_CLASS)] final class Entity implements MappingAttribute { /** * @var string|null * @psalm-var class-string<EntityRepository<T>>|null * @readonly */ public $repositoryClass; /** * @var bool * @readonly */ public $readOnly = false; /** @psalm-param class-string<EntityRepository<T>>|null $repositoryClass */ public function __construct(?string $repositoryClass = null, bool $readOnly = false) { $this->repositoryClass = $repositoryClass; $this->readOnly = $readOnly; } } orm/lib/Doctrine/ORM/Mapping/EntityListeners.php 0000644 00000001453 15120025735 0015567 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * The EntityListeners attribute specifies the callback listener classes to be used for an entity or mapped superclass. * The EntityListeners attribute may be applied to an entity class or mapped superclass. * * @Annotation * @NamedArgumentConstructor() * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class EntityListeners implements MappingAttribute { /** * Specifies the names of the entity listeners. * * @var array<string> * @readonly */ public $value = []; /** @param array<string> $value */ public function __construct(array $value = []) { $this->value = $value; } } orm/lib/Doctrine/ORM/Mapping/FieldResult.php 0000644 00000001017 15120025735 0014640 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; /** * Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class. * * @Annotation * @Target("ANNOTATION") */ final class FieldResult implements MappingAttribute { /** * Name of the column in the SELECT clause. * * @var string */ public $name; /** * Name of the persistent field or property of the class. * * @var string */ public $column; } orm/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php 0000644 00000000345 15120025735 0016554 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; /** * @Annotation * @Target("CLASS") */ #[Attribute(Attribute::TARGET_CLASS)] final class HasLifecycleCallbacks implements MappingAttribute { } orm/lib/Doctrine/ORM/Mapping/Index.php 0000644 00000002477 15120025735 0013500 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * @Annotation * @NamedArgumentConstructor() * @Target("ANNOTATION") */ #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final class Index implements MappingAttribute { /** * @var string|null * @readonly */ public $name; /** * @var array<string>|null * @readonly */ public $columns; /** * @var array<string>|null * @readonly */ public $fields; /** * @var array<string>|null * @readonly */ public $flags; /** * @var array<string,mixed>|null * @readonly */ public $options; /** * @param array<string>|null $columns * @param array<string>|null $fields * @param array<string>|null $flags * @param array<string,mixed>|null $options */ public function __construct( ?array $columns = null, ?array $fields = null, ?string $name = null, ?array $flags = null, ?array $options = null ) { $this->columns = $columns; $this->fields = $fields; $this->name = $name; $this->flags = $flags; $this->options = $options; } } orm/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php 0000644 00000004347 15120025735 0017700 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Mapping; use Doctrine\Instantiator\Instantiator; use ReflectionProperty; use ReturnTypeWillChange; /** * Acts as a proxy to a nested Property structure, making it look like * just a single scalar property. * * This way value objects "just work" without UnitOfWork, Persisters or Hydrators * needing any changes. * * TODO: Move this class into Common\Reflection */ class ReflectionEmbeddedProperty extends ReflectionProperty { /** @var ReflectionProperty reflection property of the class where the embedded object has to be put */ private $parentProperty; /** @var ReflectionProperty reflection property of the embedded object */ private $childProperty; /** @var string name of the embedded class to be eventually instantiated */ private $embeddedClass; /** @var Instantiator|null */ private $instantiator; /** @param string $embeddedClass */ public function __construct(ReflectionProperty $parentProperty, ReflectionProperty $childProperty, $embeddedClass) { $this->parentProperty = $parentProperty; $this->childProperty = $childProperty; $this->embeddedClass = (string) $embeddedClass; parent::__construct($childProperty->getDeclaringClass()->getName(), $childProperty->getName()); } /** * {@inheritDoc} * * @return mixed */ #[ReturnTypeWillChange] public function getValue($object = null) { $embeddedObject = $this->parentProperty->getValue($object); if ($embeddedObject === null) { return null; } return $this->childProperty->getValue($embeddedObject); } /** * {@inheritDoc} * * @return void */ #[ReturnTypeWillChange] public function setValue($object, $value = null) { $embeddedObject = $this->parentProperty->getValue($object); if ($embeddedObject === null) { $this->instantiator = $this->instantiator ?: new Instantiator(); $embeddedObject = $this->instantiator->instantiate($this->embeddedClass); $this->parentProperty->setValue($object, $embeddedObject); } $this->childProperty->setValue($embeddedObject, $value); } } orm/lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php 0000644 00000003451 15120025735 0022745 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Collection; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\UnitOfWork; /** * Base class for all collection persisters. */ abstract class AbstractCollectionPersister implements CollectionPersister { /** @var EntityManagerInterface */ protected $em; /** @var Connection */ protected $conn; /** @var UnitOfWork */ protected $uow; /** * The database platform. * * @var AbstractPlatform */ protected $platform; /** @var QuoteStrategy */ protected $quoteStrategy; /** * Initializes a new instance of a class derived from AbstractCollectionPersister. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } /** * Check if entity is in a valid state for operations. * * @param object $entity * * @return bool */ protected function isValidEntityState($entity) { $entityState = $this->uow->getEntityState($entity, UnitOfWork::STATE_NEW); if ($entityState === UnitOfWork::STATE_NEW) { return false; } // If Entity is scheduled for inclusion, it is not in this collection. // We can assure that because it would have return true before on array check return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)); } } orm/lib/Doctrine/ORM/Persisters/Collection/CollectionPersister.php 0000644 00000003506 15120025735 0021262 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\PersistentCollection; /** * Define the behavior that should be implemented by all collection persisters. */ interface CollectionPersister { /** * Deletes the persistent state represented by the given collection. * * @return void */ public function delete(PersistentCollection $collection); /** * Updates the given collection, synchronizing its state with the database * by inserting, updating and deleting individual elements. * * @return void */ public function update(PersistentCollection $collection); /** * Counts the size of this persistent collection. * * @return int */ public function count(PersistentCollection $collection); /** * Slices elements. * * @param int $offset * @param int|null $length * * @return mixed[] */ public function slice(PersistentCollection $collection, $offset, $length = null); /** * Checks for existence of an element. * * @param object $element * * @return bool */ public function contains(PersistentCollection $collection, $element); /** * Checks for existence of a key. * * @param mixed $key * * @return bool */ public function containsKey(PersistentCollection $collection, $key); /** * Gets an element by key. * * @param mixed $index * * @return mixed */ public function get(PersistentCollection $collection, $index); /** * Loads association entities matching the given Criteria object. * * @return mixed[] */ public function loadCriteria(PersistentCollection $collection, Criteria $criteria); } orm/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php 0000644 00000072440 15120025735 0021226 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Collection; use BadMethodCallException; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\DBAL\Exception as DBALException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\SqlValueVisitor; use Doctrine\ORM\Query; use Doctrine\ORM\Utility\PersisterHelper; use function array_fill; use function array_pop; use function count; use function get_class; use function implode; use function in_array; use function reset; use function sprintf; /** * Persister for many-to-many collections. * * @psalm-import-type AssociationMapping from ClassMetadata */ class ManyToManyPersister extends AbstractCollectionPersister { /** * {@inheritDoc} */ public function delete(PersistentCollection $collection) { $mapping = $collection->getMapping(); if (! $mapping['isOwningSide']) { return; // ignore inverse side } $types = []; $class = $this->em->getClassMetadata($mapping['sourceEntity']); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); } $this->conn->executeStatement($this->getDeleteSQL($collection), $this->getDeleteSQLParameters($collection), $types); } /** * {@inheritDoc} */ public function update(PersistentCollection $collection) { $mapping = $collection->getMapping(); if (! $mapping['isOwningSide']) { return; // ignore inverse side } [$deleteSql, $deleteTypes] = $this->getDeleteRowSQL($collection); [$insertSql, $insertTypes] = $this->getInsertRowSQL($collection); foreach ($collection->getDeleteDiff() as $element) { $this->conn->executeStatement( $deleteSql, $this->getDeleteRowSQLParameters($collection, $element), $deleteTypes ); } foreach ($collection->getInsertDiff() as $element) { $this->conn->executeStatement( $insertSql, $this->getInsertRowSQLParameters($collection, $element), $insertTypes ); } } /** * {@inheritDoc} */ public function get(PersistentCollection $collection, $index) { $mapping = $collection->getMapping(); if (! isset($mapping['indexBy'])) { throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); } $persister = $this->uow->getEntityPersister($mapping['targetEntity']); $mappedKey = $mapping['isOwningSide'] ? $mapping['inversedBy'] : $mapping['mappedBy']; return $persister->load([$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], 0, 1); } /** * {@inheritDoc} */ public function count(PersistentCollection $collection) { $conditions = []; $params = []; $types = []; $mapping = $collection->getMapping(); $id = $this->uow->getEntityIdentifier($collection->getOwner()); $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $association = ! $mapping['isOwningSide'] ? $targetClass->associationMappings[$mapping['mappedBy']] : $mapping; $joinTableName = $this->quoteStrategy->getJoinTableName($association, $sourceClass, $this->platform); $joinColumns = ! $mapping['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $sourceClass, $this->platform); $referencedName = $joinColumn['referencedColumnName']; $conditions[] = 't.' . $columnName . ' = ?'; $params[] = $id[$sourceClass->getFieldForColumn($referencedName)]; $types[] = PersisterHelper::getTypeOfColumn($referencedName, $sourceClass, $this->em); } [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($mapping); if ($filterSql) { $conditions[] = $filterSql; } // If there is a provided criteria, make part of conditions // @todo Fix this. Current SQL returns something like: /*if ($criteria && ($expression = $criteria->getWhereExpression()) !== null) { // A join is needed on the target entity $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); $targetJoinSql = ' JOIN ' . $targetTableName . ' te' . ' ON' . implode(' AND ', $this->getOnConditionSQL($association)); // And criteria conditions needs to be added $persister = $this->uow->getEntityPersister($targetClass->name); $visitor = new SqlExpressionVisitor($persister, $targetClass); $conditions[] = $visitor->dispatch($expression); $joinTargetEntitySQL = $targetJoinSql . $joinTargetEntitySQL; }*/ $sql = 'SELECT COUNT(*)' . ' FROM ' . $joinTableName . ' t' . $joinTargetEntitySQL . ' WHERE ' . implode(' AND ', $conditions); return (int) $this->conn->fetchOne($sql, $params, $types); } /** * {@inheritDoc} */ public function slice(PersistentCollection $collection, $offset, $length = null) { $mapping = $collection->getMapping(); $persister = $this->uow->getEntityPersister($mapping['targetEntity']); return $persister->getManyToManyCollection($mapping, $collection->getOwner(), $offset, $length); } /** * {@inheritDoc} */ public function containsKey(PersistentCollection $collection, $key) { $mapping = $collection->getMapping(); if (! isset($mapping['indexBy'])) { throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); } [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictionsWithKey( $collection, (string) $key, true ); $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->conn->fetchOne($sql, $params, $types); } /** * {@inheritDoc} */ public function contains(PersistentCollection $collection, $element) { if (! $this->isValidEntityState($element)) { return false; } [$quotedJoinTable, $whereClauses, $params, $types] = $this->getJoinTableRestrictions( $collection, $element, true ); $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); return (bool) $this->conn->fetchOne($sql, $params, $types); } /** * {@inheritDoc} */ public function loadCriteria(PersistentCollection $collection, Criteria $criteria) { $mapping = $collection->getMapping(); $owner = $collection->getOwner(); $ownerMetadata = $this->em->getClassMetadata(get_class($owner)); $id = $this->uow->getEntityIdentifier($owner); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $onConditions = $this->getOnConditionSQL($mapping); $whereClauses = $params = []; $paramTypes = []; if (! $mapping['isOwningSide']) { $associationSourceClass = $targetClass; $mapping = $targetClass->associationMappings[$mapping['mappedBy']]; $sourceRelationMode = 'relationToTargetKeyColumns'; } else { $associationSourceClass = $ownerMetadata; $sourceRelationMode = 'relationToSourceKeyColumns'; } foreach ($mapping[$sourceRelationMode] as $key => $value) { $whereClauses[] = sprintf('t.%s = ?', $key); $params[] = $ownerMetadata->containsForeignIdentifier ? $id[$ownerMetadata->getFieldForColumn($value)] : $id[$ownerMetadata->fieldNames[$value]]; $paramTypes[] = PersisterHelper::getTypeOfColumn($value, $ownerMetadata, $this->em); } $parameters = $this->expandCriteriaParameters($criteria); foreach ($parameters as $parameter) { [$name, $value, $operator] = $parameter; $field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform); if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { $whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT'); } else { $whereClauses[] = sprintf('te.%s %s ?', $field, $operator); $params[] = $value; $paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0]; } } $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform); $rsm = new Query\ResultSetMappingBuilder($this->em); $rsm->addRootEntityFromClassMetadata($targetClass->name, 'te'); $sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM ' . $tableName . ' te' . ' JOIN ' . $joinTable . ' t ON' . implode(' AND ', $onConditions) . ' WHERE ' . implode(' AND ', $whereClauses); $sql .= $this->getOrderingSql($criteria, $targetClass); $sql .= $this->getLimitSql($criteria); $stmt = $this->conn->executeQuery($sql, $params, $paramTypes); return $this ->em ->newHydrator(Query::HYDRATE_OBJECT) ->hydrateAll($stmt, $rsm); } /** * Generates the filter SQL for a given mapping. * * This method is not used for actually grabbing the related entities * but when the extra-lazy collection methods are called on a filtered * association. This is why besides the many to many table we also * have to join in the actual entities table leading to additional * JOIN. * * @param mixed[] $mapping Array containing mapping information. * @psalm-param AssociationMapping $mapping * * @return string[] ordered tuple: * - JOIN condition to add to the SQL * - WHERE condition to add to the SQL * @psalm-return array{0: string, 1: string} */ public function getFilterSql($mapping) { $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); if ($filterSql === '') { return ['', '']; } // A join is needed if there is filtering on the target entity $tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform); $joinSql = ' JOIN ' . $tableName . ' te' . ' ON' . implode(' AND ', $this->getOnConditionSQL($mapping)); return [$joinSql, $filterSql]; } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = []; foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); if ($filterExpr) { $filterClauses[] = '(' . $filterExpr . ')'; } } return $filterClauses ? '(' . implode(' AND ', $filterClauses) . ')' : ''; } /** * Generate ON condition * * @param mixed[] $mapping * @psalm-param AssociationMapping $mapping * * @return string[] * @psalm-return list<string> */ protected function getOnConditionSQL($mapping) { $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $association = ! $mapping['isOwningSide'] ? $targetClass->associationMappings[$mapping['mappedBy']] : $mapping; $joinColumns = $mapping['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; $conditions = []; foreach ($joinColumns as $joinColumn) { $joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = ' t.' . $joinColumnName . ' = te.' . $refColumnName; } return $conditions; } /** @return string */ protected function getDeleteSQL(PersistentCollection $collection) { $columns = []; $mapping = $collection->getMapping(); $class = $this->em->getClassMetadata(get_class($collection->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } return 'DELETE FROM ' . $joinTable . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; } /** * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteSql. * * @return list<mixed> */ protected function getDeleteSQLParameters(PersistentCollection $collection) { $mapping = $collection->getMapping(); $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); // Optimization for single column identifier if (count($mapping['relationToSourceKeyColumns']) === 1) { return [reset($identifier)]; } // Composite identifier $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $params = []; foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { $params[] = isset($sourceClass->fieldNames[$refColumnName]) ? $identifier[$sourceClass->fieldNames[$refColumnName]] : $identifier[$sourceClass->getFieldForColumn($refColumnName)]; } return $params; } /** * Gets the SQL statement used for deleting a row from the collection. * * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array * of types for bound parameters * @psalm-return array{0: string, 1: list<string>} */ protected function getDeleteRowSQL(PersistentCollection $collection) { $mapping = $collection->getMapping(); $class = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $columns = []; $types = []; foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); } return [ 'DELETE FROM ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?', $types, ]; } /** * Gets the SQL parameters for the corresponding SQL statement to delete the given * element from the given collection. * * Internal note: Order of the parameters must be the same as the order of the columns in getDeleteRowSql. * * @param mixed $element * * @return mixed[] * @psalm-return list<mixed> */ protected function getDeleteRowSQLParameters(PersistentCollection $collection, $element) { return $this->collectJoinTableColumnParameters($collection, $element); } /** * Gets the SQL statement used for inserting a row in the collection. * * @return string[]|string[][] ordered tuple containing the SQL to be executed and an array * of types for bound parameters * @psalm-return array{0: string, 1: list<string>} */ protected function getInsertRowSQL(PersistentCollection $collection) { $columns = []; $types = []; $mapping = $collection->getMapping(); $class = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $class, $this->em); } foreach ($mapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); } return [ 'INSERT INTO ' . $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform) . ' (' . implode(', ', $columns) . ')' . ' VALUES' . ' (' . implode(', ', array_fill(0, count($columns), '?')) . ')', $types, ]; } /** * Gets the SQL parameters for the corresponding SQL statement to insert the given * element of the given collection into the database. * * Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql. * * @param object $element * * @return mixed[] * @psalm-return list<mixed> */ protected function getInsertRowSQLParameters(PersistentCollection $collection, $element) { return $this->collectJoinTableColumnParameters($collection, $element); } /** * Collects the parameters for inserting/deleting on the join table in the order * of the join table columns as specified in ManyToManyMapping#joinTableColumns. * * @param object $element * * @return mixed[] * @psalm-return list<mixed> */ private function collectJoinTableColumnParameters( PersistentCollection $collection, $element ): array { $params = []; $mapping = $collection->getMapping(); $isComposite = count($mapping['joinTableColumns']) > 2; $identifier1 = $this->uow->getEntityIdentifier($collection->getOwner()); $identifier2 = $this->uow->getEntityIdentifier($element); $class1 = $class2 = null; if ($isComposite) { $class1 = $this->em->getClassMetadata(get_class($collection->getOwner())); $class2 = $collection->getTypeClass(); } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); if (! $isComposite) { $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); continue; } if ($isRelationToSource) { $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; continue; } $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; } /** * @param bool $addFilters Whether the filter SQL should be included or not. * * @return mixed[] ordered vector: * - quoted join table name * - where clauses to be added for filtering * - parameters to be bound for filtering * - types of the parameters to be bound for filtering * @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>} */ private function getJoinTableRestrictionsWithKey( PersistentCollection $collection, string $key, bool $addFilters ): array { $filterMapping = $collection->getMapping(); $mapping = $filterMapping; $indexBy = $mapping['indexBy']; $id = $this->uow->getEntityIdentifier($collection->getOwner()); $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); if (! $mapping['isOwningSide']) { $associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']); $mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']]; $joinColumns = $mapping['joinTable']['joinColumns']; $sourceRelationMode = 'relationToTargetKeyColumns'; $targetRelationMode = 'relationToSourceKeyColumns'; } else { $associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $joinColumns = $mapping['joinTable']['inverseJoinColumns']; $sourceRelationMode = 'relationToSourceKeyColumns'; $targetRelationMode = 'relationToTargetKeyColumns'; } $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform) . ' t'; $whereClauses = []; $params = []; $types = []; $joinNeeded = ! in_array($indexBy, $targetClass->identifier, true); if ($joinNeeded) { // extra join needed if indexBy is not a @id $joinConditions = []; foreach ($joinColumns as $joinTableColumn) { $joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName']; } $tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); $quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions); $columnName = $targetClass->getColumnName($indexBy); $whereClauses[] = 'tr.' . $columnName . ' = ?'; $params[] = $key; $types[] = PersisterHelper::getTypeOfColumn($columnName, $targetClass, $this->em); } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { if (isset($mapping[$sourceRelationMode][$joinTableColumn])) { $column = $mapping[$sourceRelationMode][$joinTableColumn]; $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; $params[] = $sourceClass->containsForeignIdentifier ? $id[$sourceClass->getFieldForColumn($column)] : $id[$sourceClass->fieldNames[$column]]; $types[] = PersisterHelper::getTypeOfColumn($column, $sourceClass, $this->em); } elseif (! $joinNeeded) { $column = $mapping[$targetRelationMode][$joinTableColumn]; $whereClauses[] = 't.' . $joinTableColumn . ' = ?'; $params[] = $key; $types[] = PersisterHelper::getTypeOfColumn($column, $targetClass, $this->em); } } if ($addFilters) { [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); if ($filterSql) { $quotedJoinTable .= ' ' . $joinTargetEntitySQL; $whereClauses[] = $filterSql; } } return [$quotedJoinTable, $whereClauses, $params, $types]; } /** * @param bool $addFilters Whether the filter SQL should be included or not. * @param object $element * * @return mixed[] ordered vector: * - quoted join table name * - where clauses to be added for filtering * - parameters to be bound for filtering * - types of the parameters to be bound for filtering * @psalm-return array{0: string, 1: list<string>, 2: list<mixed>, 3: list<string>} */ private function getJoinTableRestrictions( PersistentCollection $collection, $element, bool $addFilters ): array { $filterMapping = $collection->getMapping(); $mapping = $filterMapping; if (! $mapping['isOwningSide']) { $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); $sourceId = $this->uow->getEntityIdentifier($element); $targetId = $this->uow->getEntityIdentifier($collection->getOwner()); $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; } else { $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $sourceId = $this->uow->getEntityIdentifier($collection->getOwner()); $targetId = $this->uow->getEntityIdentifier($element); } $quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $sourceClass, $this->platform); $whereClauses = []; $params = []; $types = []; foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $whereClauses[] = ($addFilters ? 't.' : '') . $joinTableColumn . ' = ?'; if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { $targetColumn = $mapping['relationToTargetKeyColumns'][$joinTableColumn]; $params[] = $targetId[$targetClass->getFieldForColumn($targetColumn)]; $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); continue; } // relationToSourceKeyColumns $targetColumn = $mapping['relationToSourceKeyColumns'][$joinTableColumn]; $params[] = $sourceId[$sourceClass->getFieldForColumn($targetColumn)]; $types[] = PersisterHelper::getTypeOfColumn($targetColumn, $sourceClass, $this->em); } if ($addFilters) { $quotedJoinTable .= ' t'; [$joinTargetEntitySQL, $filterSql] = $this->getFilterSql($filterMapping); if ($filterSql) { $quotedJoinTable .= ' ' . $joinTargetEntitySQL; $whereClauses[] = $filterSql; } } return [$quotedJoinTable, $whereClauses, $params, $types]; } /** * Expands Criteria Parameters by walking the expressions and grabbing all * parameters and types from it. * * @return mixed[][] */ private function expandCriteriaParameters(Criteria $criteria): array { $expression = $criteria->getWhereExpression(); if ($expression === null) { return []; } $valueVisitor = new SqlValueVisitor(); $valueVisitor->dispatch($expression); [, $types] = $valueVisitor->getParamsAndTypes(); return $types; } private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string { $orderings = $criteria->getOrderings(); if ($orderings) { $orderBy = []; foreach ($orderings as $name => $direction) { $field = $this->quoteStrategy->getColumnName( $name, $targetClass, $this->platform ); $orderBy[] = $field . ' ' . $direction; } return ' ORDER BY ' . implode(', ', $orderBy); } return ''; } /** @throws DBALException */ private function getLimitSql(Criteria $criteria): string { $limit = $criteria->getMaxResults(); $offset = $criteria->getFirstResult(); return $this->platform->modifyLimitQuery('', $limit, $offset ?? 0); } } orm/lib/Doctrine/ORM/Persisters/Collection/OneToManyPersister.php 0000644 00000022717 15120025735 0021045 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Collection; use BadMethodCallException; use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Utility\PersisterHelper; use function array_merge; use function array_reverse; use function array_values; use function assert; use function implode; use function is_int; use function is_string; /** * Persister for one-to-many collections. */ class OneToManyPersister extends AbstractCollectionPersister { /** * {@inheritDoc} * * @return int|null */ public function delete(PersistentCollection $collection) { // The only valid case here is when you have weak entities. In this // scenario, you have @OneToMany with orphanRemoval=true, and replacing // the entire collection with a new would trigger this operation. $mapping = $collection->getMapping(); if (! $mapping['orphanRemoval']) { // Handling non-orphan removal should never happen, as @OneToMany // can only be inverse side. For owning side one to many, it is // required to have a join table, which would classify as a ManyToManyPersister. return; } $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); return $targetClass->isInheritanceTypeJoined() ? $this->deleteJoinedEntityCollection($collection) : $this->deleteEntityCollection($collection); } /** * {@inheritDoc} */ public function update(PersistentCollection $collection) { // This can never happen. One to many can only be inverse side. // For owning side one to many, it is required to have a join table, // then classifying it as a ManyToManyPersister. return; } /** * {@inheritDoc} */ public function get(PersistentCollection $collection, $index) { $mapping = $collection->getMapping(); if (! isset($mapping['indexBy'])) { throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); } $persister = $this->uow->getEntityPersister($mapping['targetEntity']); return $persister->load( [ $mapping['mappedBy'] => $collection->getOwner(), $mapping['indexBy'] => $index, ], null, $mapping, [], null, 1 ); } /** * {@inheritDoc} */ public function count(PersistentCollection $collection) { $mapping = $collection->getMapping(); $persister = $this->uow->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); return $persister->count($criteria); } /** * {@inheritDoc} */ public function slice(PersistentCollection $collection, $offset, $length = null) { $mapping = $collection->getMapping(); $persister = $this->uow->getEntityPersister($mapping['targetEntity']); return $persister->getOneToManyCollection($mapping, $collection->getOwner(), $offset, $length); } /** * {@inheritDoc} */ public function containsKey(PersistentCollection $collection, $key) { $mapping = $collection->getMapping(); if (! isset($mapping['indexBy'])) { throw new BadMethodCallException('Selecting a collection by index is only supported on indexed collections.'); } $persister = $this->uow->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. $criteria = new Criteria(); $criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); $criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key)); return (bool) $persister->count($criteria); } /** * {@inheritDoc} */ public function contains(PersistentCollection $collection, $element) { if (! $this->isValidEntityState($element)) { return false; } $mapping = $collection->getMapping(); $persister = $this->uow->getEntityPersister($mapping['targetEntity']); // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. $criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner())); return $persister->exists($element, $criteria); } /** * {@inheritDoc} */ public function loadCriteria(PersistentCollection $collection, Criteria $criteria) { throw new BadMethodCallException('Filtering a collection by Criteria is not supported by this CollectionPersister.'); } /** @throws DBALException */ private function deleteEntityCollection(PersistentCollection $collection): int { $mapping = $collection->getMapping(); $identifier = $this->uow->getEntityIdentifier($collection->getOwner()); $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $columns = []; $parameters = []; $types = []; foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $parameters[] = $identifier[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])]; $types[] = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $sourceClass, $this->em); } $statement = 'DELETE FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?'; $numAffected = $this->conn->executeStatement($statement, $parameters, $types); assert(is_int($numAffected)); return $numAffected; } /** * Delete Class Table Inheritance entities. * A temporary table is needed to keep IDs to be deleted in both parent and child class' tables. * * Thanks Steve Ebersole (Hibernate) for idea on how to tackle reliably this scenario, we owe him a beer! =) * * @throws DBALException */ private function deleteJoinedEntityCollection(PersistentCollection $collection): int { $mapping = $collection->getMapping(); $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); // 1) Build temporary table DDL $tempTable = $this->platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); $columnDefinitions = []; foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = [ 'notnull' => true, 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $this->em)), ]; } $statement = $this->platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $this->platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; $this->conn->executeStatement($statement); // 2) Build insert table records into temporary table $query = $this->em->createQuery( ' SELECT t0.' . implode(', t0.', $rootClass->getIdentifierFieldNames()) . ' FROM ' . $targetClass->name . ' t0 WHERE t0.' . $mapping['mappedBy'] . ' = :owner' )->setParameter('owner', $collection->getOwner()); $sql = $query->getSQL(); assert(is_string($sql)); $statement = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ') ' . $sql; $parameters = array_values($sourceClass->getIdentifierValues($collection->getOwner())); $numDeleted = $this->conn->executeStatement($statement, $parameters); // 3) Delete records on each table in the hierarchy $classNames = array_merge($targetClass->parentClasses, [$targetClass->name], $targetClass->subClasses); foreach (array_reverse($classNames) as $className) { $tableName = $this->quoteStrategy->getTableName($this->em->getClassMetadata($className), $this->platform); $statement = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ')' . ' IN (SELECT ' . $idColumnList . ' FROM ' . $tempTable . ')'; $this->conn->executeStatement($statement); } // 4) Drop temporary table $statement = $this->platform->getDropTemporaryTableSQL($tempTable); $this->conn->executeStatement($statement); assert(is_int($numDeleted)); return $numDeleted; } } orm/lib/Doctrine/ORM/Persisters/Entity/AbstractEntityInheritancePersister.php 0000644 00000005146 15120025735 0023464 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use function sprintf; /** * Base class for entity persisters that implement a certain inheritance mapping strategy. * All these persisters are assumed to use a discriminator column to discriminate entity * types in the hierarchy. */ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister { /** * {@inheritDoc} */ protected function prepareInsertData($entity) { $data = parent::prepareInsertData($entity); // Populate the discriminator column $discColumn = $this->class->getDiscriminatorColumn(); $this->columnTypes[$discColumn['name']] = $discColumn['type']; $data[$this->getDiscriminatorColumnTableName()][$discColumn['name']] = $this->class->discriminatorValue; return $data; } /** * Gets the name of the table that contains the discriminator column. * * @return string The table name. */ abstract protected function getDiscriminatorColumnTableName(); /** * {@inheritDoc} */ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $tableAlias = $alias === 'r' ? '' : $alias; $fieldMapping = $class->fieldMappings[$field]; $columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']); $sql = sprintf( '%s.%s', $this->getSQLTableAlias($class->name, $tableAlias), $this->quoteStrategy->getColumnName($field, $class, $this->platform) ); $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field, $class->name); if (isset($fieldMapping['requireSQLConversion'])) { $type = Type::getType($fieldMapping['type']); $sql = $type->convertToPHPValueSQL($sql, $this->platform); } return $sql . ' AS ' . $columnAlias; } /** * @param string $tableAlias * @param string $joinColumnName * @param string $quotedColumnName * @param string $type * * @return string */ protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $quotedColumnName, $type) { $columnAlias = $this->getSQLColumnAlias($joinColumnName); $this->currentPersisterContext->rsm->addMetaResult('r', $columnAlias, $joinColumnName, false, $type); return $tableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; } } orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php 0000644 00000225243 15120025735 0020572 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use BackedEnum; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Exception\CantUseInOperatorOnCompositeKeys; use Doctrine\ORM\Persisters\Exception\InvalidOrientation; use Doctrine\ORM\Persisters\Exception\UnrecognizedField; use Doctrine\ORM\Persisters\SqlExpressionVisitor; use Doctrine\ORM\Persisters\SqlValueVisitor; use Doctrine\ORM\Query; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Repository\Exception\InvalidFindByCall; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Utility\IdentifierFlattener; use Doctrine\ORM\Utility\PersisterHelper; use LengthException; use function array_combine; use function array_keys; use function array_map; use function array_merge; use function array_search; use function array_unique; use function array_values; use function assert; use function count; use function implode; use function is_array; use function is_object; use function reset; use function spl_object_id; use function sprintf; use function str_contains; use function strtoupper; use function trim; /** * A BasicEntityPersister maps an entity to a single table in a relational database. * * A persister is always responsible for a single entity type. * * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent * state of entities onto a relational database when the UnitOfWork is committed, * as well as for basic querying of entities and their associations (not DQL). * * The persisting operations that are invoked during a commit of a UnitOfWork to * persist the persistent entity state are: * * - {@link addInsert} : To schedule an entity for insertion. * - {@link executeInserts} : To execute all scheduled insertions. * - {@link update} : To update the persistent state of an entity. * - {@link delete} : To delete the persistent state of an entity. * * As can be seen from the above list, insertions are batched and executed all at once * for increased efficiency. * * The querying operations invoked during a UnitOfWork, either through direct find * requests or lazy-loading, are the following: * * - {@link load} : Loads (the state of) a single, managed entity. * - {@link loadAll} : Loads multiple, managed entities. * - {@link loadOneToOneEntity} : Loads a one/many-to-one entity association (lazy-loading). * - {@link loadOneToManyCollection} : Loads a one-to-many entity association (lazy-loading). * - {@link loadManyToManyCollection} : Loads a many-to-many entity association (lazy-loading). * * The BasicEntityPersister implementation provides the default behavior for * persisting and querying entities that are mapped to a single database table. * * Subclasses can be created to provide custom persisting and querying strategies, * i.e. spanning multiple tables. * * @psalm-import-type AssociationMapping from ClassMetadata */ class BasicEntityPersister implements EntityPersister { /** @var array<string,string> */ private static $comparisonMap = [ Comparison::EQ => '= %s', Comparison::NEQ => '!= %s', Comparison::GT => '> %s', Comparison::GTE => '>= %s', Comparison::LT => '< %s', Comparison::LTE => '<= %s', Comparison::IN => 'IN (%s)', Comparison::NIN => 'NOT IN (%s)', Comparison::CONTAINS => 'LIKE %s', Comparison::STARTS_WITH => 'LIKE %s', Comparison::ENDS_WITH => 'LIKE %s', ]; /** * Metadata object that describes the mapping of the mapped entity class. * * @var ClassMetadata */ protected $class; /** * The underlying DBAL Connection of the used EntityManager. * * @var Connection $conn */ protected $conn; /** * The database platform. * * @var AbstractPlatform */ protected $platform; /** * The EntityManager instance. * * @var EntityManagerInterface */ protected $em; /** * Queued inserts. * * @psalm-var array<int, object> */ protected $queuedInserts = []; /** * The map of column names to DBAL mapping types of all prepared columns used * when INSERTing or UPDATEing an entity. * * @see prepareInsertData($entity) * @see prepareUpdateData($entity) * * @var mixed[] */ protected $columnTypes = []; /** * The map of quoted column names. * * @see prepareInsertData($entity) * @see prepareUpdateData($entity) * * @var mixed[] */ protected $quotedColumns = []; /** * The INSERT SQL statement used for entities handled by this persister. * This SQL is only generated once per request, if at all. * * @var string|null */ private $insertSql; /** * The quote strategy. * * @var QuoteStrategy */ protected $quoteStrategy; /** * The IdentifierFlattener used for manipulating identifiers * * @var IdentifierFlattener */ private $identifierFlattener; /** @var CachedPersisterContext */ protected $currentPersisterContext; /** @var CachedPersisterContext */ private $limitsHandlingContext; /** @var CachedPersisterContext */ private $noLimitsContext; /** * Initializes a new <tt>BasicEntityPersister</tt> that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. */ public function __construct(EntityManagerInterface $em, ClassMetadata $class) { $this->em = $em; $this->class = $class; $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); $this->noLimitsContext = $this->currentPersisterContext = new CachedPersisterContext( $class, new Query\ResultSetMapping(), false ); $this->limitsHandlingContext = new CachedPersisterContext( $class, new Query\ResultSetMapping(), true ); } /** * {@inheritDoc} */ public function getClassMetadata() { return $this->class; } /** * {@inheritDoc} */ public function getResultSetMapping() { return $this->currentPersisterContext->rsm; } /** * {@inheritDoc} */ public function addInsert($entity) { $this->queuedInserts[spl_object_id($entity)] = $entity; } /** * {@inheritDoc} */ public function getInserts() { return $this->queuedInserts; } /** * {@inheritDoc} */ public function executeInserts() { if (! $this->queuedInserts) { return []; } $postInsertIds = []; $idGenerator = $this->class->idGenerator; $isPostInsertId = $idGenerator->isPostInsertGenerator(); $stmt = $this->conn->prepare($this->getInsertSQL()); $tableName = $this->class->getTableName(); foreach ($this->queuedInserts as $entity) { $insertData = $this->prepareInsertData($entity); if (isset($insertData[$tableName])) { $paramIndex = 1; foreach ($insertData[$tableName] as $column => $value) { $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$column]); } } $stmt->executeStatement(); if ($isPostInsertId) { $generatedId = $idGenerator->generateId($this->em, $entity); $id = [$this->class->identifier[0] => $generatedId]; $postInsertIds[] = [ 'generatedId' => $generatedId, 'entity' => $entity, ]; } else { $id = $this->class->getIdentifierValues($entity); } if ($this->class->requiresFetchAfterChange) { $this->assignDefaultVersionAndUpsertableValues($entity, $id); } } $this->queuedInserts = []; return $postInsertIds; } /** * Retrieves the default version value which was created * by the preceding INSERT statement and assigns it back in to the * entities version field if the given entity is versioned. * Also retrieves values of columns marked as 'non insertable' and / or * 'not updatable' and assigns them back to the entities corresponding fields. * * @param object $entity * @param mixed[] $id * * @return void */ protected function assignDefaultVersionAndUpsertableValues($entity, array $id) { $values = $this->fetchVersionAndNotUpsertableValues($this->class, $id); foreach ($values as $field => $value) { $value = Type::getType($this->class->fieldMappings[$field]['type'])->convertToPHPValue($value, $this->platform); $this->class->setFieldValue($entity, $field, $value); } } /** * Fetches the current version value of a versioned entity and / or the values of fields * marked as 'not insertable' and / or 'not updatable'. * * @param ClassMetadata $versionedClass * @param mixed[] $id * * @return mixed */ protected function fetchVersionAndNotUpsertableValues($versionedClass, array $id) { $columnNames = []; foreach ($this->class->fieldMappings as $key => $column) { if (isset($column['generated']) || ($this->class->isVersioned && $key === $versionedClass->versionField)) { $columnNames[$key] = $this->quoteStrategy->getColumnName($key, $versionedClass, $this->platform); } } $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); // FIXME: Order with composite keys might not be correct $sql = 'SELECT ' . implode(', ', $columnNames) . ' FROM ' . $tableName . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); $values = $this->conn->fetchNumeric( $sql, array_values($flatId), $this->extractIdentifierTypes($id, $versionedClass) ); if ($values === false) { throw new LengthException('Unexpected empty result for database query.'); } $values = array_combine(array_keys($columnNames), $values); if (! $values) { throw new LengthException('Unexpected number of database columns.'); } return $values; } /** * @param mixed[] $id * * @return int[]|null[]|string[] * @psalm-return list<int|string|null> */ private function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array { $types = []; foreach ($id as $field => $value) { $types = array_merge($types, $this->getTypes($field, $value, $versionedClass)); } return $types; } /** * {@inheritDoc} */ public function update($entity) { $tableName = $this->class->getTableName(); $updateData = $this->prepareUpdateData($entity); if (! isset($updateData[$tableName])) { return; } $data = $updateData[$tableName]; if (! $data) { return; } $isVersioned = $this->class->isVersioned; $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $this->updateTable($entity, $quotedTableName, $data, $isVersioned); if ($this->class->requiresFetchAfterChange) { $id = $this->class->getIdentifierValues($entity); $this->assignDefaultVersionAndUpsertableValues($entity, $id); } } /** * Performs an UPDATE statement for an entity on a specific table. * The UPDATE can optionally be versioned, which requires the entity to have a version field. * * @param object $entity The entity object being updated. * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. * @param mixed[] $updateData The map of columns to update (column => value). * @param bool $versioned Whether the UPDATE should be versioned. * * @throws UnrecognizedField * @throws OptimisticLockException */ final protected function updateTable( $entity, $quotedTableName, array $updateData, $versioned = false ): void { $set = []; $types = []; $params = []; foreach ($updateData as $columnName => $value) { $placeholder = '?'; $column = $columnName; switch (true) { case isset($this->class->fieldNames[$columnName]): $fieldName = $this->class->fieldNames[$columnName]; $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); if (isset($this->class->fieldMappings[$fieldName]['requireSQLConversion'])) { $type = Type::getType($this->columnTypes[$columnName]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); } break; case isset($this->quotedColumns[$columnName]): $column = $this->quotedColumns[$columnName]; break; } $params[] = $value; $set[] = $column . ' = ' . $placeholder; $types[] = $this->columnTypes[$columnName]; } $where = []; $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); foreach ($this->class->identifier as $idField) { if (! isset($this->class->associationMappings[$idField])) { $params[] = $identifier[$idField]; $types[] = $this->class->fieldMappings[$idField]['type']; $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); continue; } $params[] = $identifier[$idField]; $where[] = $this->quoteStrategy->getJoinColumnName( $this->class->associationMappings[$idField]['joinColumns'][0], $this->class, $this->platform ); $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); $targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em); if ($targetType === []) { throw UnrecognizedField::byFullyQualifiedName($this->class->name, $targetMapping->identifier[0]); } $types[] = reset($targetType); } if ($versioned) { $versionField = $this->class->versionField; assert($versionField !== null); $versionFieldType = $this->class->fieldMappings[$versionField]['type']; $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); $where[] = $versionColumn; $types[] = $this->class->fieldMappings[$versionField]['type']; $params[] = $this->class->reflFields[$versionField]->getValue($entity); switch ($versionFieldType) { case Types::SMALLINT: case Types::INTEGER: case Types::BIGINT: $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; break; case Types::DATETIME_MUTABLE: $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; break; } } $sql = 'UPDATE ' . $quotedTableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; $result = $this->conn->executeStatement($sql, $params, $types); if ($versioned && ! $result) { throw OptimisticLockException::lockFailed($entity); } } /** * @param array<mixed> $identifier * @param string[] $types * * @todo Add check for platform if it supports foreign keys/cascading. */ protected function deleteJoinTableRecords(array $identifier, array $types): void { foreach ($this->class->associationMappings as $mapping) { if ($mapping['type'] !== ClassMetadata::MANY_TO_MANY || isset($mapping['isOnDeleteCascade'])) { continue; } // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! $selfReferential = ($mapping['targetEntity'] === $mapping['sourceEntity']); $class = $this->class; $association = $mapping; $otherColumns = []; $otherKeys = []; $keys = []; if (! $mapping['isOwningSide']) { $class = $this->em->getClassMetadata($mapping['targetEntity']); $association = $class->associationMappings[$mapping['mappedBy']]; } $joinColumns = $mapping['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; if ($selfReferential) { $otherColumns = ! $mapping['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; } foreach ($joinColumns as $joinColumn) { $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } foreach ($otherColumns as $joinColumn) { $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); $this->conn->delete($joinTableName, array_combine($keys, $identifier), $types); if ($selfReferential) { $this->conn->delete($joinTableName, array_combine($otherKeys, $identifier), $types); } } } /** * {@inheritDoc} */ public function delete($entity) { $class = $this->class; $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $tableName = $this->quoteStrategy->getTableName($class, $this->platform); $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); $id = array_combine($idColumns, $identifier); $types = $this->getClassIdentifiersTypes($class); $this->deleteJoinTableRecords($identifier, $types); return (bool) $this->conn->delete($tableName, $id, $types); } /** * Prepares the changeset of an entity for database insertion (UPDATE). * * The changeset is obtained from the currently running UnitOfWork. * * During this preparation the array that is passed as the second parameter is filled with * <columnName> => <value> pairs, grouped by table name. * * Example: * <code> * array( * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), * ... * ) * </code> * * @param object $entity The entity for which to prepare the data. * @param bool $isInsert Whether the data to be prepared refers to an insert statement. * * @return mixed[][] The prepared data. * @psalm-return array<string, array<array-key, mixed|null>> */ protected function prepareUpdateData($entity, bool $isInsert = false) { $versionField = null; $result = []; $uow = $this->em->getUnitOfWork(); $versioned = $this->class->isVersioned; if ($versioned !== false) { $versionField = $this->class->versionField; } foreach ($uow->getEntityChangeSet($entity) as $field => $change) { if (isset($versionField) && $versionField === $field) { continue; } if (isset($this->class->embeddedClasses[$field])) { continue; } $newVal = $change[1]; if (! isset($this->class->associationMappings[$field])) { $fieldMapping = $this->class->fieldMappings[$field]; $columnName = $fieldMapping['columnName']; if (! $isInsert && isset($fieldMapping['notUpdatable'])) { continue; } if ($isInsert && isset($fieldMapping['notInsertable'])) { continue; } $this->columnTypes[$columnName] = $fieldMapping['type']; $result[$this->getOwningTable($field)][$columnName] = $newVal; continue; } $assoc = $this->class->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. if (! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { continue; } if ($newVal !== null) { $oid = spl_object_id($newVal); if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { // The associated entity $newVal is not yet persisted, so we must // set $newVal = null, in order to insert a null value and schedule an // extra update on the UnitOfWork. $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); $newVal = null; } } $newValId = null; if ($newVal !== null) { $newValId = $uow->getEntityIdentifier($newVal); } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningTable = $this->getOwningTable($field); foreach ($assoc['joinColumns'] as $joinColumn) { $sourceColumn = $joinColumn['name']; $targetColumn = $joinColumn['referencedColumnName']; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $this->quotedColumns[$sourceColumn] = $quotedColumn; $this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); $result[$owningTable][$sourceColumn] = $newValId ? $newValId[$targetClass->getFieldForColumn($targetColumn)] : null; } } return $result; } /** * Prepares the data changeset of a managed entity for database insertion (initial INSERT). * The changeset of the entity is obtained from the currently running UnitOfWork. * * The default insert data preparation is the same as for updates. * * @see prepareUpdateData * * @param object $entity The entity for which to prepare the data. * * @return mixed[][] The prepared data for the tables to update. * @psalm-return array<string, mixed[]> */ protected function prepareInsertData($entity) { return $this->prepareUpdateData($entity, true); } /** * {@inheritDoc} */ public function getOwningTable($fieldName) { return $this->class->getTableName(); } /** * {@inheritDoc} */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null) { $this->switchPersisterContext(null, $limit); $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); [$params, $types] = $this->expandParameters($criteria); $stmt = $this->conn->executeQuery($sql, $params, $types); if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; } $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, $hints); return $entities ? $entities[0] : null; } /** * {@inheritDoc} */ public function loadById(array $identifier, $entity = null) { return $this->load($identifier, $entity); } /** * {@inheritDoc} */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = []) { $foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity']); if ($foundEntity !== false) { return $foundEntity; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if ($assoc['isOwningSide']) { $isInverseSingleValued = $assoc['inversedBy'] && ! $targetClass->isCollectionValuedAssociation($assoc['inversedBy']); // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = []; if ($isInverseSingleValued) { $hints['fetched']['r'][$assoc['inversedBy']] = true; } $targetEntity = $this->load($identifier, null, $assoc, $hints); // Complete bidirectional association, if necessary if ($targetEntity !== null && $isInverseSingleValued) { $targetClass->reflFields[$assoc['inversedBy']]->setValue($targetEntity, $sourceEntity); } return $targetEntity; } $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); $computedIdentifier = []; // TRICKY: since the association is specular source and target are flipped foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) { throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } $computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); } $targetEntity = $this->load($computedIdentifier, null, $assoc); if ($targetEntity !== null) { $targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity); } return $targetEntity; } /** * {@inheritDoc} */ public function refresh(array $id, $entity, $lockMode = null) { $sql = $this->getSelectSQL($id, null, $lockMode); [$params, $types] = $this->expandParameters($id); $stmt = $this->conn->executeQuery($sql, $params, $types); $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [Query::HINT_REFRESH => true]); } /** * {@inheritDoc} */ public function count($criteria = []) { $sql = $this->getCountSQL($criteria); [$params, $types] = $criteria instanceof Criteria ? $this->expandCriteriaParameters($criteria) : $this->expandParameters($criteria); return (int) $this->conn->executeQuery($sql, $params, $types)->fetchOne(); } /** * {@inheritDoc} */ public function loadCriteria(Criteria $criteria) { $orderBy = $criteria->getOrderings(); $limit = $criteria->getMaxResults(); $offset = $criteria->getFirstResult(); $query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); [$params, $types] = $this->expandCriteriaParameters($criteria); $stmt = $this->conn->executeQuery($query, $params, $types); $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); } /** * {@inheritDoc} */ public function expandCriteriaParameters(Criteria $criteria) { $expression = $criteria->getWhereExpression(); $sqlParams = []; $sqlTypes = []; if ($expression === null) { return [$sqlParams, $sqlTypes]; } $valueVisitor = new SqlValueVisitor(); $valueVisitor->dispatch($expression); [, $types] = $valueVisitor->getParamsAndTypes(); foreach ($types as $type) { [$field, $value, $operator] = $type; if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) { continue; } $sqlParams = array_merge($sqlParams, $this->getValues($value)); $sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class)); } return [$sqlParams, $sqlTypes]; } /** * {@inheritDoc} */ public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null) { $this->switchPersisterContext($offset, $limit); $sql = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); [$params, $types] = $this->expandParameters($criteria); $stmt = $this->conn->executeQuery($sql, $params, $types); $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true]); } /** * {@inheritDoc} */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $this->switchPersisterContext($offset, $limit); $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromResult($assoc, $stmt); } /** * Loads an array of entities from a given DBAL statement. * * @param mixed[] $assoc * * @return mixed[] */ private function loadArrayFromResult(array $assoc, Result $stmt): array { $rsm = $this->currentPersisterContext->rsm; $hints = [UnitOfWork::HINT_DEFEREAGERLOAD => true]; if (isset($assoc['indexBy'])) { $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); } /** * Hydrates a collection from a given DBAL statement. * * @param mixed[] $assoc * * @return mixed[] */ private function loadCollectionFromStatement( array $assoc, Result $stmt, PersistentCollection $coll ): array { $rsm = $this->currentPersisterContext->rsm; $hints = [ UnitOfWork::HINT_DEFEREAGERLOAD => true, 'collection' => $coll, ]; if (isset($assoc['indexBy'])) { $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. $rsm->addIndexBy('r', $assoc['indexBy']); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); } /** * {@inheritDoc} */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $collection); } /** * @param object $sourceEntity * @psalm-param array<string, mixed> $assoc * * @return Result * * @throws MappingException */ private function getManyToManyStatement( array $assoc, $sourceEntity, ?int $offset = null, ?int $limit = null ) { $this->switchPersisterContext($offset, $limit); $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $class = $sourceClass; $association = $assoc; $criteria = []; $parameters = []; if (! $assoc['isOwningSide']) { $class = $this->em->getClassMetadata($assoc['targetEntity']); $association = $class->associationMappings[$assoc['mappedBy']]; } $joinColumns = $assoc['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); foreach ($joinColumns as $joinColumn) { $sourceKeyColumn = $joinColumn['referencedColumnName']; $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); switch (true) { case $sourceClass->containsForeignIdentifier: $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } break; case isset($sourceClass->fieldNames[$sourceKeyColumn]): $field = $sourceClass->fieldNames[$sourceKeyColumn]; $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); break; default: throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, $sourceKeyColumn ); } $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; $parameters[] = [ 'value' => $value, 'field' => $field, 'class' => $sourceClass, ]; } $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); [$params, $types] = $this->expandToManyParameters($parameters); return $this->conn->executeQuery($sql, $params, $types); } /** * {@inheritDoc} */ public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null) { $this->switchPersisterContext($offset, $limit); $lockSql = ''; $joinSql = ''; $orderBySql = ''; if ($assoc !== null && $assoc['type'] === ClassMetadata::MANY_TO_MANY) { $joinSql = $this->getSelectManyToManyJoinSQL($assoc); } if (isset($assoc['orderBy'])) { $orderBy = $assoc['orderBy']; } if ($orderBy) { $orderBySql = $this->getOrderBySQL($orderBy, $this->getSQLTableAlias($this->class->name)); } $conditionSql = $criteria instanceof Criteria ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria, $assoc); switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = ' ' . $this->platform->getReadLockSQL(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = ' ' . $this->platform->getWriteLockSQL(); break; } $columnList = $this->getSelectColumnsSQL(); $tableAlias = $this->getSQLTableAlias($this->class->name); $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); if ($filterSql !== '') { $conditionSql = $conditionSql ? $conditionSql . ' AND ' . $filterSql : $filterSql; } $select = 'SELECT ' . $columnList; $from = ' FROM ' . $tableName . ' ' . $tableAlias; $join = $this->currentPersisterContext->selectJoinSql . $joinSql; $where = ($conditionSql ? ' WHERE ' . $conditionSql : ''); $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); $query = $select . $lock . $join . $where . $orderBySql; return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; } /** * {@inheritDoc} */ public function getCountSQL($criteria = []) { $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $tableAlias = $this->getSQLTableAlias($this->class->name); $conditionSql = $criteria instanceof Criteria ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria); $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); if ($filterSql !== '') { $conditionSql = $conditionSql ? $conditionSql . ' AND ' . $filterSql : $filterSql; } return 'SELECT COUNT(*) ' . 'FROM ' . $tableName . ' ' . $tableAlias . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); } /** * Gets the ORDER BY SQL snippet for ordered collections. * * @psalm-param array<string, string> $orderBy * * @throws InvalidOrientation * @throws InvalidFindByCall * @throws UnrecognizedField */ final protected function getOrderBySQL(array $orderBy, string $baseTableAlias): string { $orderByList = []; foreach ($orderBy as $fieldName => $orientation) { $orientation = strtoupper(trim($orientation)); if ($orientation !== 'ASC' && $orientation !== 'DESC') { throw InvalidOrientation::fromClassNameAndField($this->class->name, $fieldName); } if (isset($this->class->fieldMappings[$fieldName])) { $tableAlias = isset($this->class->fieldMappings[$fieldName]['inherited']) ? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited']) : $baseTableAlias; $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; continue; } if (isset($this->class->associationMappings[$fieldName])) { if (! $this->class->associationMappings[$fieldName]['isOwningSide']) { throw InvalidFindByCall::fromInverseSideUsage($this->class->name, $fieldName); } $tableAlias = isset($this->class->associationMappings[$fieldName]['inherited']) ? $this->getSQLTableAlias($this->class->associationMappings[$fieldName]['inherited']) : $baseTableAlias; foreach ($this->class->associationMappings[$fieldName]['joinColumns'] as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; } continue; } throw UnrecognizedField::byFullyQualifiedName($this->class->name, $fieldName); } return ' ORDER BY ' . implode(', ', $orderByList); } /** * Gets the SQL fragment with the list of columns to select when querying for * an entity in this persister. * * Subclasses should override this method to alter or change the select column * list SQL fragment. Note that in the implementation of BasicEntityPersister * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. * Subclasses may or may not do the same. * * @return string The SQL fragment. */ protected function getSelectColumnsSQL() { if ($this->currentPersisterContext->selectColumnListSql !== null) { return $this->currentPersisterContext->selectColumnListSql; } $columnList = []; $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); // r for root // Add regular columns to select list foreach ($this->class->fieldNames as $field) { $columnList[] = $this->getSelectColumnSQL($field, $this->class); } $this->currentPersisterContext->selectJoinSql = ''; $eagerAliasCounter = 0; foreach ($this->class->associationMappings as $assocField => $assoc) { $assocColumnSQL = $this->getSelectColumnAssociationSQL($assocField, $assoc, $this->class); if ($assocColumnSQL) { $columnList[] = $assocColumnSQL; } $isAssocToOneInverseSide = $assoc['type'] & ClassMetadata::TO_ONE && ! $assoc['isOwningSide']; $isAssocFromOneEager = $assoc['type'] !== ClassMetadata::MANY_TO_MANY && $assoc['fetch'] === ClassMetadata::FETCH_EAGER; if (! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { continue; } if ((($assoc['type'] & ClassMetadata::TO_MANY) > 0) && $this->currentPersisterContext->handlesLimits) { continue; } $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); if ($eagerEntity->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } $assocAlias = 'e' . ($eagerAliasCounter++); $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); foreach ($eagerEntity->fieldNames as $field) { $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); } foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) { $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL( $eagerAssocField, $eagerAssoc, $eagerEntity, $assocAlias ); if ($eagerAssocColumnSQL) { $columnList[] = $eagerAssocColumnSQL; } } $association = $assoc; $joinCondition = []; if (isset($assoc['indexBy'])) { $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc['indexBy']); } if (! $assoc['isOwningSide']) { $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); $association = $eagerEntity->getAssociationMapping($assoc['mappedBy']); } $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); if ($assoc['isOwningSide']) { $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias); $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association['joinColumns']); foreach ($association['joinColumns'] as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity']) . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; } // Add filter SQL $filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias); if ($filterSql) { $joinCondition[] = $filterSql; } } else { $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN'; foreach ($association['joinColumns'] as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol; } } $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON '; $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition); } $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); return $this->currentPersisterContext->selectColumnListSql; } /** * Gets the SQL join fragment used when selecting entities from an association. * * @param string $field * @param AssociationMapping $assoc * @param string $alias * * @return string */ protected function getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') { if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { return ''; } $columnList = []; $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $isIdentifier = isset($assoc['id']) && $assoc['id'] === true; $sqlTableAlias = $this->getSQLTableAlias($class->name, ($alias === 'r' ? '' : $alias)); foreach ($assoc['joinColumns'] as $joinColumn) { $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); $type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); $this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $joinColumn['name'], $isIdentifier, $type); $columnList[] = sprintf('%s.%s AS %s', $sqlTableAlias, $quotedColumn, $resultColumnName); } return implode(', ', $columnList); } /** * Gets the SQL join fragment used when selecting entities from a * many-to-many association. * * @psalm-param AssociationMapping $manyToMany * * @return string */ protected function getSelectManyToManyJoinSQL(array $manyToMany) { $conditions = []; $association = $manyToMany; $sourceTableAlias = $this->getSQLTableAlias($this->class->name); if (! $manyToMany['isOwningSide']) { $targetEntity = $this->em->getClassMetadata($manyToMany['targetEntity']); $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); $joinColumns = $manyToMany['isOwningSide'] ? $association['joinTable']['inverseJoinColumns'] : $association['joinTable']['joinColumns']; foreach ($joinColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; } return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); } /** * {@inheritDoc} */ public function getInsertSQL() { if ($this->insertSql !== null) { return $this->insertSql; } $columns = $this->getInsertColumnList(); $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); if (empty($columns)) { $identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform); $this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn); return $this->insertSql; } $values = []; $columns = array_unique($columns); foreach ($columns as $column) { $placeholder = '?'; if ( isset($this->class->fieldNames[$column]) && isset($this->columnTypes[$this->class->fieldNames[$column]]) && isset($this->class->fieldMappings[$this->class->fieldNames[$column]]['requireSQLConversion']) ) { $type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); } $values[] = $placeholder; } $columns = implode(', ', $columns); $values = implode(', ', $values); $this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values); return $this->insertSql; } /** * Gets the list of columns to put in the INSERT SQL statement. * * Subclasses should override this method to alter or change the list of * columns placed in the INSERT statements used by the persister. * * @return string[] The list of columns. * @psalm-return list<string> */ protected function getInsertColumnList() { $columns = []; foreach ($this->class->reflFields as $name => $field) { if ($this->class->isVersioned && $this->class->versionField === $name) { continue; } if (isset($this->class->embeddedClasses[$name])) { continue; } if (isset($this->class->associationMappings[$name])) { $assoc = $this->class->associationMappings[$name]; if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { foreach ($assoc['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } continue; } if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name) { if (isset($this->class->fieldMappings[$name]['notInsertable'])) { continue; } $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); $this->columnTypes[$name] = $this->class->fieldMappings[$name]['type']; } } return $columns; } /** * Gets the SQL snippet of a qualified column name for the given field name. * * @param string $field The field name. * @param ClassMetadata $class The class that declares this field. The table this class is * mapped to must own the column for the given field. * @param string $alias * * @return string */ protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') { $root = $alias === 'r' ? '' : $alias; $tableAlias = $this->getSQLTableAlias($class->name, $root); $fieldMapping = $class->fieldMappings[$field]; $sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform)); $columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']); $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field); if (! empty($fieldMapping['enumType'])) { $this->currentPersisterContext->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']); } if (isset($fieldMapping['requireSQLConversion'])) { $type = Type::getType($fieldMapping['type']); $sql = $type->convertToPHPValueSQL($sql, $this->platform); } return $sql . ' AS ' . $columnAlias; } /** * Gets the SQL table alias for the given class name. * * @param string $className * @param string $assocName * * @return string The SQL table alias. * * @todo Reconsider. Binding table aliases to class names is not such a good idea. */ protected function getSQLTableAlias($className, $assocName = '') { if ($assocName) { $className .= '#' . $assocName; } if (isset($this->currentPersisterContext->sqlTableAliases[$className])) { return $this->currentPersisterContext->sqlTableAliases[$className]; } $tableAlias = 't' . $this->currentPersisterContext->sqlAliasCounter++; $this->currentPersisterContext->sqlTableAliases[$className] = $tableAlias; return $tableAlias; } /** * {@inheritDoc} */ public function lock(array $criteria, $lockMode) { $lockSql = ''; $conditionSql = $this->getSelectConditionSQL($criteria); switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = $this->platform->getReadLockSQL(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = $this->platform->getWriteLockSQL(); break; } $lock = $this->getLockTablesSql($lockMode); $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; $sql = 'SELECT 1 ' . $lock . $where . $lockSql; [$params, $types] = $this->expandParameters($criteria); $this->conn->executeQuery($sql, $params, $types); } /** * Gets the FROM and optionally JOIN conditions to lock the entity managed by this persister. * * @param int|null $lockMode One of the Doctrine\DBAL\LockMode::* constants. * @psalm-param LockMode::*|null $lockMode * * @return string */ protected function getLockTablesSql($lockMode) { if ($lockMode === null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9466', 'Passing null as argument to %s is deprecated, pass LockMode::NONE instead.', __METHOD__ ); $lockMode = LockMode::NONE; } return $this->platform->appendLockHint( 'FROM ' . $this->quoteStrategy->getTableName($this->class, $this->platform) . ' ' . $this->getSQLTableAlias($this->class->name), $lockMode ); } /** * Gets the Select Where Condition from a Criteria object. * * @return string */ protected function getSelectConditionCriteriaSQL(Criteria $criteria) { $expression = $criteria->getWhereExpression(); if ($expression === null) { return ''; } $visitor = new SqlExpressionVisitor($this, $this->class); return $visitor->dispatch($expression); } /** * {@inheritDoc} */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) { $selectedColumns = []; $columns = $this->getSelectConditionStatementColumnSQL($field, $assoc); if (count($columns) > 1 && $comparison === Comparison::IN) { /* * @todo try to support multi-column IN expressions. * Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B')) */ throw CantUseInOperatorOnCompositeKeys::create(); } foreach ($columns as $column) { $placeholder = '?'; if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) { $type = Type::getType($this->class->fieldMappings[$field]['type']); $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform); } if ($comparison !== null) { // special case null value handling if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) { $selectedColumns[] = $column . ' IS NULL'; continue; } if ($comparison === Comparison::NEQ && $value === null) { $selectedColumns[] = $column . ' IS NOT NULL'; continue; } $selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); continue; } if (is_array($value)) { $in = sprintf('%s IN (%s)', $column, $placeholder); if (array_search(null, $value, true) !== false) { $selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column); continue; } $selectedColumns[] = $in; continue; } if ($value === null) { $selectedColumns[] = sprintf('%s IS NULL', $column); continue; } $selectedColumns[] = sprintf('%s = %s', $column, $placeholder); } return implode(' AND ', $selectedColumns); } /** * Builds the left-hand-side of a where condition statement. * * @psalm-param AssociationMapping|null $assoc * * @return string[] * @psalm-return list<string> * * @throws InvalidFindByCall * @throws UnrecognizedField */ private function getSelectConditionStatementColumnSQL( string $field, ?array $assoc = null ): array { if (isset($this->class->fieldMappings[$field])) { $className = $this->class->fieldMappings[$field]['inherited'] ?? $this->class->name; return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)]; } if (isset($this->class->associationMappings[$field])) { $association = $this->class->associationMappings[$field]; // Many-To-Many requires join table check for joinColumn $columns = []; $class = $this->class; if ($association['type'] === ClassMetadata::MANY_TO_MANY) { if (! $association['isOwningSide']) { $association = $assoc; } $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); $joinColumns = $assoc['isOwningSide'] ? $association['joinTable']['joinColumns'] : $association['joinTable']['inverseJoinColumns']; foreach ($joinColumns as $joinColumn) { $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } else { if (! $association['isOwningSide']) { throw InvalidFindByCall::fromInverseSideUsage( $this->class->name, $field ); } $className = $association['inherited'] ?? $this->class->name; foreach ($association['joinColumns'] as $joinColumn) { $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } return $columns; } if ($assoc !== null && ! str_contains($field, ' ') && ! str_contains($field, '(')) { // very careless developers could potentially open up this normally hidden api for userland attacks, // therefore checking for spaces and function calls which are not allowed. // found a join column condition, not really a "field" return [$field]; } throw UnrecognizedField::byFullyQualifiedName($this->class->name, $field); } /** * Gets the conditional SQL fragment used in the WHERE clause when selecting * entities in this persister. * * Subclasses are supposed to override this method if they intend to change * or alter the criteria by which entities are selected. * * @param AssociationMapping|null $assoc * @psalm-param array<string, mixed> $criteria * @psalm-param array<string, mixed>|null $assoc * * @return string */ protected function getSelectConditionSQL(array $criteria, $assoc = null) { $conditions = []; foreach ($criteria as $field => $value) { $conditions[] = $this->getSelectConditionStatementSQL($field, $value, $assoc); } return implode(' AND ', $conditions); } /** * {@inheritDoc} */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { $this->switchPersisterContext($offset, $limit); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); return $this->loadArrayFromResult($assoc, $stmt); } /** * {@inheritDoc} */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $collection); } /** * Builds criteria and execute SQL statement to fetch the one to many entities from. * * @param object $sourceEntity * @psalm-param AssociationMapping $assoc */ private function getOneToManyStatement( array $assoc, $sourceEntity, ?int $offset = null, ?int $limit = null ): Result { $this->switchPersisterContext($offset, $limit); $criteria = []; $parameters = []; $owningAssoc = $this->class->associationMappings[$assoc['mappedBy']]; $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); $tableAlias = $this->getSQLTableAlias($owningAssoc['inherited'] ?? $this->class->name); foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; } $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; $parameters[] = [ 'value' => $value, 'field' => $field, 'class' => $sourceClass, ]; continue; } $field = $sourceClass->fieldNames[$sourceKeyColumn]; $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; $parameters[] = [ 'value' => $value, 'field' => $field, 'class' => $sourceClass, ]; } $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); [$params, $types] = $this->expandToManyParameters($parameters); return $this->conn->executeQuery($sql, $params, $types); } /** * {@inheritDoc} */ public function expandParameters($criteria) { $params = []; $types = []; foreach ($criteria as $field => $value) { if ($value === null) { continue; // skip null values. } $types = array_merge($types, $this->getTypes($field, $value, $this->class)); $params = array_merge($params, $this->getValues($value)); } return [$params, $types]; } /** * Expands the parameters from the given criteria and use the correct binding types if found, * specialized for OneToMany or ManyToMany associations. * * @param mixed[][] $criteria an array of arrays containing following: * - field to which each criterion will be bound * - value to be bound * - class to which the field belongs to * * @return mixed[][] * @psalm-return array{0: array, 1: list<int|string|null>} */ private function expandToManyParameters(array $criteria): array { $params = []; $types = []; foreach ($criteria as $criterion) { if ($criterion['value'] === null) { continue; // skip null values. } $types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class'])); $params = array_merge($params, $this->getValues($criterion['value'])); } return [$params, $types]; } /** * Infers field types to be used by parameter type casting. * * @param mixed $value * * @return int[]|null[]|string[] * @psalm-return list<int|string|null> * * @throws QueryException */ private function getTypes(string $field, $value, ClassMetadata $class): array { $types = []; switch (true) { case isset($class->fieldMappings[$field]): $types = array_merge($types, [$class->fieldMappings[$field]['type']]); break; case isset($class->associationMappings[$field]): $assoc = $class->associationMappings[$field]; $class = $this->em->getClassMetadata($assoc['targetEntity']); if (! $assoc['isOwningSide']) { $assoc = $class->associationMappings[$assoc['mappedBy']]; $class = $this->em->getClassMetadata($assoc['targetEntity']); } $columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY ? $assoc['relationToTargetKeyColumns'] : $assoc['sourceToTargetKeyColumns']; foreach ($columns as $column) { $types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em); } break; default: $types[] = null; break; } if (is_array($value)) { return array_map(static function ($type) { $type = Type::getType($type); return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET; }, $types); } return $types; } /** * Retrieves the parameters that identifies a value. * * @param mixed $value * * @return mixed[] */ private function getValues($value): array { if (is_array($value)) { $newValue = []; foreach ($value as $itemValue) { $newValue = array_merge($newValue, $this->getValues($itemValue)); } return [$newValue]; } return $this->getIndividualValue($value); } /** * Retrieves an individual parameter value. * * @param mixed $value * * @psalm-return list<mixed> */ private function getIndividualValue($value): array { if (! is_object($value)) { return [$value]; } if ($value instanceof BackedEnum) { return [$value->value]; } $valueClass = ClassUtils::getClass($value); if ($this->em->getMetadataFactory()->isTransient($valueClass)) { return [$value]; } $class = $this->em->getClassMetadata($valueClass); if ($class->isIdentifierComposite) { $newValue = []; foreach ($class->getIdentifierValues($value) as $innerValue) { $newValue = array_merge($newValue, $this->getValues($innerValue)); } return $newValue; } return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)]; } /** * {@inheritDoc} */ public function exists($entity, ?Criteria $extraConditions = null) { $criteria = $this->class->getIdentifierValues($entity); if (! $criteria) { return false; } $alias = $this->getSQLTableAlias($this->class->name); $sql = 'SELECT 1 ' . $this->getLockTablesSql(LockMode::NONE) . ' WHERE ' . $this->getSelectConditionSQL($criteria); [$params, $types] = $this->expandParameters($criteria); if ($extraConditions !== null) { $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); [$criteriaParams, $criteriaTypes] = $this->expandCriteriaParameters($extraConditions); $params = array_merge($params, $criteriaParams); $types = array_merge($types, $criteriaTypes); } $filterSql = $this->generateFilterConditionSQL($this->class, $alias); if ($filterSql) { $sql .= ' AND ' . $filterSql; } return (bool) $this->conn->fetchOne($sql, $params, $types); } /** * Generates the appropriate join SQL for the given join column. * * @param array[] $joinColumns The join columns definition of an association. * @psalm-param array<array<string, mixed>> $joinColumns * * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. */ protected function getJoinSQLForJoinColumns($joinColumns) { // if one of the join columns is nullable, return left join foreach ($joinColumns as $joinColumn) { if (! isset($joinColumn['nullable']) || $joinColumn['nullable']) { return 'LEFT JOIN'; } } return 'INNER JOIN'; } /** * @param string $columnName * * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->currentPersisterContext->sqlAliasCounter++, $this->platform); } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { $filterClauses = []; foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); if ($filterExpr !== '') { $filterClauses[] = '(' . $filterExpr . ')'; } } $sql = implode(' AND ', $filterClauses); return $sql ? '(' . $sql . ')' : ''; // Wrap again to avoid "X or Y and FilterConditionSQL" } /** * Switches persister context according to current query offset/limits * * This is due to the fact that to-many associations cannot be fetch-joined when a limit is involved * * @param int|null $offset * @param int|null $limit * * @return void */ protected function switchPersisterContext($offset, $limit) { if ($offset === null && $limit === null) { $this->currentPersisterContext = $this->noLimitsContext; return; } $this->currentPersisterContext = $this->limitsHandlingContext; } /** * @return string[] * @psalm-return list<string> */ protected function getClassIdentifiersTypes(ClassMetadata $class): array { $entityManager = $this->em; return array_map( static function ($fieldName) use ($class, $entityManager): string { $types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager); assert(isset($types[0])); return $types[0]; }, $class->identifier ); } } orm/lib/Doctrine/ORM/Persisters/Entity/CachedPersisterContext.php 0000644 00000004136 15120025735 0021064 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\Mapping\ClassMetadata; /** * A swappable persister context to use as a container for the current * generated query/resultSetMapping/type binding information. * * This class is a utility class to be used only by the persister API * * This object is highly mutable due to performance reasons. Same reasoning * behind its properties being public. */ class CachedPersisterContext { /** * Metadata object that describes the mapping of the mapped entity class. * * @var \Doctrine\ORM\Mapping\ClassMetadata */ public $class; /** * ResultSetMapping that is used for all queries. Is generated lazily once per request. * * @var ResultSetMapping */ public $rsm; /** * The SELECT column list SQL fragment used for querying entities by this persister. * This SQL fragment is only generated once per request, if at all. * * @var string|null */ public $selectColumnListSql; /** * The JOIN SQL fragment used to eagerly load all many-to-one and one-to-one * associations configured as FETCH_EAGER, as well as all inverse one-to-one associations. * * @var string */ public $selectJoinSql; /** * Counter for creating unique SQL table and column aliases. * * @var int */ public $sqlAliasCounter = 0; /** * Map from class names (FQCN) to the corresponding generated SQL table aliases. * * @var array<class-string, string> */ public $sqlTableAliases = []; /** * Whether this persistent context is considering limit operations applied to the selection queries * * @var bool */ public $handlesLimits; /** @param bool $handlesLimits */ public function __construct( ClassMetadata $class, ResultSetMapping $rsm, $handlesLimits ) { $this->class = $class; $this->rsm = $rsm; $this->handlesLimits = (bool) $handlesLimits; } } orm/lib/Doctrine/ORM/Persisters/Entity/EntityPersister.php 0000644 00000027042 15120025735 0017625 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query\ResultSetMapping; /** * Entity persister interface * Define the behavior that should be implemented by all entity persisters. * * @psalm-import-type AssociationMapping from ClassMetadata */ interface EntityPersister { /** @return ClassMetadata */ public function getClassMetadata(); /** * Gets the ResultSetMapping used for hydration. * * @return ResultSetMapping */ public function getResultSetMapping(); /** * Get all queued inserts. * * @return object[] */ public function getInserts(); /** * Gets the INSERT SQL used by the persister to persist a new entity. * * @return string * * @TODO It should not be here. * But its necessary since JoinedSubclassPersister#executeInserts invoke the root persister. */ public function getInsertSQL(); /** * Gets the SELECT SQL to select one or more entities by a set of field criteria. * * @param mixed[]|Criteria $criteria * @param int|null $lockMode * @param int|null $limit * @param int|null $offset * @param mixed[]|null $orderBy * @psalm-param AssociationMapping|null $assoc * @psalm-param LockMode::*|null $lockMode * * @return string */ public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null); /** * Get the COUNT SQL to count entities (optionally based on a criteria) * * @param mixed[]|Criteria $criteria * * @return string */ public function getCountSQL($criteria = []); /** * Expands the parameters from the given criteria and use the correct binding types if found. * * @param string[] $criteria * * @psalm-return array{list<mixed>, list<int|string|null>} */ public function expandParameters($criteria); /** * Expands Criteria Parameters by walking the expressions and grabbing all parameters and types from it. * * @psalm-return array{list<mixed>, list<int|string|null>} */ public function expandCriteriaParameters(Criteria $criteria); /** * Gets the SQL WHERE condition for matching a field with a given value. * * @param string $field * @param mixed $value * @param AssociationMapping|null $assoc * @param string|null $comparison * * @return string */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null); /** * Adds an entity to the queued insertions. * The entity remains queued until {@link executeInserts} is invoked. * * @param object $entity The entity to queue for insertion. * * @return void */ public function addInsert($entity); /** * Executes all queued entity insertions and returns any generated post-insert * identifiers that were created as a result of the insertions. * * If no inserts are queued, invoking this method is a NOOP. * * @psalm-return list<array{ * generatedId: int, * entity: object * }> An array of any generated post-insert IDs. This will be * an empty array if the entity class does not use the * IDENTITY generation strategy. */ public function executeInserts(); /** * Updates a managed entity. The entity is updated according to its current changeset * in the running UnitOfWork. If there is no changeset, nothing is updated. * * @param object $entity The entity to update. * * @return void */ public function update($entity); /** * Deletes a managed entity. * * The entity to delete must be managed and have a persistent identifier. * The deletion happens instantaneously. * * Subclasses may override this method to customize the semantics of entity deletion. * * @param object $entity The entity to delete. * * @return bool TRUE if the entity got deleted in the database, FALSE otherwise. */ public function delete($entity); /** * Count entities (optionally filtered by a criteria) * * @param mixed[]|Criteria $criteria * * @return int */ public function count($criteria = []); /** * Gets the name of the table that owns the column the given field is mapped to. * * The default implementation in BasicEntityPersister always returns the name * of the table the entity type of this persister is mapped to, since an entity * is always persisted to a single table with a BasicEntityPersister. * * @param string $fieldName The field name. * * @return string The table name. */ public function getOwningTable($fieldName); /** * Loads an entity by a list of field criteria. * * @param mixed[] $criteria The criteria by which to load the entity. * @param object|null $entity The entity to load the data into. If not specified, * a new entity is created. * @param AssociationMapping|null $assoc The association that connects the entity * to load to another entity, if any. * @param mixed[] $hints Hints for entity creation. * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants * or NULL if no specific lock mode should be used * for loading the entity. * @param int|null $limit Limit number of results. * @param string[]|null $orderBy Criteria to order by. * @psalm-param array<string, mixed> $criteria * @psalm-param array<string, mixed> $hints * @psalm-param LockMode::*|null $lockMode * @psalm-param array<string, string>|null $orderBy * * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. * * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? */ public function load( array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null ); /** * Loads an entity by identifier. * * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. * @psalm-param array<string, mixed> $identifier The entity identifier. * * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. * * @todo Check parameters */ public function loadById(array $identifier, $entity = null); /** * Loads an entity of this persister's mapped class as part of a single-valued * association from another entity. * * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). * @psalm-param array<string, mixed> $identifier The identifier of the entity to load. Must be provided if * the association to load represents the owning side, otherwise * the identifier is derived from the $sourceEntity. * @psalm-param AssociationMapping $assoc The association to load. * * @return object The loaded and managed entity instance or NULL if the entity can not be found. * * @throws MappingException */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = []); /** * Refreshes a managed entity. * * @param object $entity The entity to refresh. * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants * or NULL if no specific lock mode should be used * for refreshing the managed entity. * @psalm-param array<string, mixed> $id The identifier of the entity as an * associative array from column or * field names to values. * @psalm-param LockMode::*|null $lockMode * * @return void */ public function refresh(array $id, $entity, $lockMode = null); /** * Loads Entities matching the given Criteria object. * * @return mixed[] */ public function loadCriteria(Criteria $criteria); /** * Loads a list of entities by a list of field criteria. * * @param int|null $limit * @param int|null $offset * @psalm-param array<string, string>|null $orderBy * @psalm-param array<string, mixed> $criteria */ public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null); /** * Gets (sliced or full) elements of the given collection. * * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * @psalm-param AssociationMapping $assoc * * @return mixed[] */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null); /** * Loads a collection of entities of a many-to-many association. * * @param object $sourceEntity The entity that owns the collection. * @param PersistentCollection $collection The collection to fill. * @psalm-param AssociationMapping $assoc The association mapping of the association being loaded. * * @return mixed[] */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection); /** * Loads a collection of entities in a one-to-many association. * * @param object $sourceEntity * @param PersistentCollection $collection The collection to load/fill. * @psalm-param AssociationMapping $assoc * * @return mixed */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection); /** * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. * * @param int $lockMode One of the Doctrine\DBAL\LockMode::* constants. * @psalm-param array<string, mixed> $criteria * @psalm-param LockMode::* $lockMode * * @return void */ public function lock(array $criteria, $lockMode); /** * Returns an array with (sliced or full list) of elements in the specified collection. * * @param object $sourceEntity * @param int|null $offset * @param int|null $limit * @psalm-param AssociationMapping $assoc * * @return mixed[] */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null); /** * Checks whether the given managed entity exists in the database. * * @param object $entity * * @return bool TRUE if the entity exists in the database, FALSE otherwise. */ public function exists($entity, ?Criteria $extraConditions = null); } orm/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php 0000644 00000053431 15120025735 0021262 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Utility\PersisterHelper; use function array_combine; use function implode; /** * The joined subclass persister maps a single entity instance to several tables in the * database as it is defined by the <tt>Class Table Inheritance</tt> strategy. * * @see https://martinfowler.com/eaaCatalog/classTableInheritance.html */ class JoinedSubclassPersister extends AbstractEntityInheritancePersister { use SQLResultCasing; /** * Map that maps column names to the table names that own them. * This is mainly a temporary cache, used during a single request. * * @psalm-var array<string, string> */ private $owningTableMap = []; /** * Map of table to quoted table names. * * @psalm-var array<string, string> */ private $quotedTableMap = []; /** * {@inheritDoc} */ protected function getDiscriminatorColumnTableName() { $class = $this->class->name !== $this->class->rootEntityName ? $this->em->getClassMetadata($this->class->rootEntityName) : $this->class; return $class->getTableName(); } /** * This function finds the ClassMetadata instance in an inheritance hierarchy * that is responsible for enabling versioning. */ private function getVersionedClassMetadata(): ClassMetadata { if (isset($this->class->fieldMappings[$this->class->versionField]['inherited'])) { $definingClassName = $this->class->fieldMappings[$this->class->versionField]['inherited']; return $this->em->getClassMetadata($definingClassName); } return $this->class; } /** * Gets the name of the table that owns the column the given field is mapped to. * * @param string $fieldName * * @return string */ public function getOwningTable($fieldName) { if (isset($this->owningTableMap[$fieldName])) { return $this->owningTableMap[$fieldName]; } switch (true) { case isset($this->class->associationMappings[$fieldName]['inherited']): $cm = $this->em->getClassMetadata($this->class->associationMappings[$fieldName]['inherited']); break; case isset($this->class->fieldMappings[$fieldName]['inherited']): $cm = $this->em->getClassMetadata($this->class->fieldMappings[$fieldName]['inherited']); break; default: $cm = $this->class; break; } $tableName = $cm->getTableName(); $quotedTableName = $this->quoteStrategy->getTableName($cm, $this->platform); $this->owningTableMap[$fieldName] = $tableName; $this->quotedTableMap[$tableName] = $quotedTableName; return $tableName; } /** * {@inheritDoc} */ public function executeInserts() { if (! $this->queuedInserts) { return []; } $postInsertIds = []; $idGenerator = $this->class->idGenerator; $isPostInsertId = $idGenerator->isPostInsertGenerator(); $rootClass = $this->class->name !== $this->class->rootEntityName ? $this->em->getClassMetadata($this->class->rootEntityName) : $this->class; // Prepare statement for the root table $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->name); $rootTableName = $rootClass->getTableName(); $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL()); // Prepare statements for sub tables. $subTableStmts = []; if ($rootClass !== $this->class) { $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL()); } foreach ($this->class->parentClasses as $parentClassName) { $parentClass = $this->em->getClassMetadata($parentClassName); $parentTableName = $parentClass->getTableName(); if ($parentClass !== $rootClass) { $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClassName); $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL()); } } // Execute all inserts. For each entity: // 1) Insert on root table // 2) Insert on sub tables foreach ($this->queuedInserts as $entity) { $insertData = $this->prepareInsertData($entity); // Execute insert on root table $paramIndex = 1; foreach ($insertData[$rootTableName] as $columnName => $value) { $rootTableStmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); } $rootTableStmt->executeStatement(); if ($isPostInsertId) { $generatedId = $idGenerator->generateId($this->em, $entity); $id = [$this->class->identifier[0] => $generatedId]; $postInsertIds[] = [ 'generatedId' => $generatedId, 'entity' => $entity, ]; } else { $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); } if ($this->class->requiresFetchAfterChange) { $this->assignDefaultVersionAndUpsertableValues($entity, $id); } // Execute inserts on subtables. // The order doesn't matter because all child tables link to the root table via FK. foreach ($subTableStmts as $tableName => $stmt) { $paramIndex = 1; $data = $insertData[$tableName] ?? []; foreach ($id as $idName => $idVal) { $type = $this->columnTypes[$idName] ?? Types::STRING; $stmt->bindValue($paramIndex++, $idVal, $type); } foreach ($data as $columnName => $value) { if (! isset($id[$columnName])) { $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$columnName]); } } $stmt->executeStatement(); } } $this->queuedInserts = []; return $postInsertIds; } /** * {@inheritDoc} */ public function update($entity) { $updateData = $this->prepareUpdateData($entity); if (! $updateData) { return; } $isVersioned = $this->class->isVersioned; $versionedClass = $this->getVersionedClassMetadata(); $versionedTable = $versionedClass->getTableName(); foreach ($updateData as $tableName => $data) { $tableName = $this->quotedTableMap[$tableName]; $versioned = $isVersioned && $versionedTable === $tableName; $this->updateTable($entity, $tableName, $data, $versioned); } if ($this->class->requiresFetchAfterChange) { // Make sure the table with the version column is updated even if no columns on that // table were affected. if ($isVersioned && ! isset($updateData[$versionedTable])) { $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); $this->updateTable($entity, $tableName, [], true); } $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $this->assignDefaultVersionAndUpsertableValues($entity, $identifiers); } } /** * {@inheritDoc} */ public function delete($entity) { $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $id = array_combine($this->class->getIdentifierColumnNames(), $identifier); $types = $this->getClassIdentifiersTypes($this->class); $this->deleteJoinTableRecords($identifier, $types); // If the database platform supports FKs, just // delete the row from the root table. Cascades do the rest. if ($this->platform->supportsForeignKeyConstraints()) { $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); $rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform); $rootTypes = $this->getClassIdentifiersTypes($rootClass); return (bool) $this->conn->delete($rootTable, $id, $rootTypes); } // Delete from all tables individually, starting from this class' table up to the root table. $rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform); $rootTypes = $this->getClassIdentifiersTypes($this->class); $affectedRows = $this->conn->delete($rootTable, $id, $rootTypes); foreach ($this->class->parentClasses as $parentClass) { $parentMetadata = $this->em->getClassMetadata($parentClass); $parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform); $parentTypes = $this->getClassIdentifiersTypes($parentMetadata); $this->conn->delete($parentTable, $id, $parentTypes); } return (bool) $affectedRows; } /** * {@inheritDoc} */ public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null) { $this->switchPersisterContext($offset, $limit); $baseTableAlias = $this->getSQLTableAlias($this->class->name); $joinSql = $this->getJoinSql($baseTableAlias); if ($assoc !== null && $assoc['type'] === ClassMetadata::MANY_TO_MANY) { $joinSql .= $this->getSelectManyToManyJoinSQL($assoc); } $conditionSql = $criteria instanceof Criteria ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria, $assoc); $filterSql = $this->generateFilterConditionSQL( $this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName) ); // If the current class in the root entity, add the filters if ($filterSql) { $conditionSql .= $conditionSql ? ' AND ' . $filterSql : $filterSql; } $orderBySql = ''; if ($assoc !== null && isset($assoc['orderBy'])) { $orderBy = $assoc['orderBy']; } if ($orderBy) { $orderBySql = $this->getOrderBySQL($orderBy, $baseTableAlias); } $lockSql = ''; switch ($lockMode) { case LockMode::PESSIMISTIC_READ: $lockSql = ' ' . $this->platform->getReadLockSQL(); break; case LockMode::PESSIMISTIC_WRITE: $lockSql = ' ' . $this->platform->getWriteLockSQL(); break; } $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $from = ' FROM ' . $tableName . ' ' . $baseTableAlias; $where = $conditionSql !== '' ? ' WHERE ' . $conditionSql : ''; $lock = $this->platform->appendLockHint($from, $lockMode ?? LockMode::NONE); $columnList = $this->getSelectColumnsSQL(); $query = 'SELECT ' . $columnList . $lock . $joinSql . $where . $orderBySql; return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql; } /** * {@inheritDoc} */ public function getCountSQL($criteria = []) { $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); $baseTableAlias = $this->getSQLTableAlias($this->class->name); $joinSql = $this->getJoinSql($baseTableAlias); $conditionSql = $criteria instanceof Criteria ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria); $filterSql = $this->generateFilterConditionSQL($this->em->getClassMetadata($this->class->rootEntityName), $this->getSQLTableAlias($this->class->rootEntityName)); if ($filterSql !== '') { $conditionSql = $conditionSql ? $conditionSql . ' AND ' . $filterSql : $filterSql; } return 'SELECT COUNT(*) ' . 'FROM ' . $tableName . ' ' . $baseTableAlias . $joinSql . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); } /** * {@inheritDoc} */ protected function getLockTablesSql($lockMode) { $joinSql = ''; $identifierColumns = $this->class->getIdentifierColumnNames(); $baseTableAlias = $this->getSQLTableAlias($this->class->name); // INNER JOIN parent tables foreach ($this->class->parentClasses as $parentClassName) { $conditions = []; $tableAlias = $this->getSQLTableAlias($parentClassName); $parentClass = $this->em->getClassMetadata($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumns as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } return parent::getLockTablesSql($lockMode) . $joinSql; } /** * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly. * * @return string */ protected function getSelectColumnsSQL() { // Create the column list fragment only once if ($this->currentPersisterContext->selectColumnListSql !== null) { return $this->currentPersisterContext->selectColumnListSql; } $columnList = []; $discrColumn = $this->class->getDiscriminatorColumn(); $discrColumnName = $discrColumn['name']; $discrColumnType = $discrColumn['type']; $baseTableAlias = $this->getSQLTableAlias($this->class->name); $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); // Add regular columns foreach ($this->class->fieldMappings as $fieldName => $mapping) { $class = isset($mapping['inherited']) ? $this->em->getClassMetadata($mapping['inherited']) : $this->class; $columnList[] = $this->getSelectColumnSQL($fieldName, $class); } // Add foreign key columns foreach ($this->class->associationMappings as $mapping) { if (! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE)) { continue; } $tableAlias = isset($mapping['inherited']) ? $this->getSQLTableAlias($mapping['inherited']) : $baseTableAlias; $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); foreach ($mapping['joinColumns'] as $joinColumn) { $columnList[] = $this->getSelectJoinColumnSQL( $tableAlias, $joinColumn['name'], $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform), PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em) ); } } // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult). $tableAlias = $this->class->rootEntityName === $this->class->name ? $baseTableAlias : $this->getSQLTableAlias($this->class->rootEntityName); $columnList[] = $tableAlias . '.' . $discrColumnName; // sub tables foreach ($this->class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClassName); // Add subclass columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); } // Add join columns (foreign keys) foreach ($subClass->associationMappings as $mapping) { if ( ! $mapping['isOwningSide'] || ! ($mapping['type'] & ClassMetadata::TO_ONE) || isset($mapping['inherited']) ) { continue; } $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); foreach ($mapping['joinColumns'] as $joinColumn) { $columnList[] = $this->getSelectJoinColumnSQL( $tableAlias, $joinColumn['name'], $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em) ); } } } $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); return $this->currentPersisterContext->selectColumnListSql; } /** * {@inheritDoc} */ protected function getInsertColumnList() { // Identifier columns must always come first in the column list of subclasses. $columns = $this->class->parentClasses ? $this->class->getIdentifierColumnNames() : []; foreach ($this->class->reflFields as $name => $field) { if ( isset($this->class->fieldMappings[$name]['inherited']) && ! isset($this->class->fieldMappings[$name]['id']) || isset($this->class->associationMappings[$name]['inherited']) || ($this->class->isVersioned && $this->class->versionField === $name) || isset($this->class->embeddedClasses[$name]) ) { continue; } if (isset($this->class->associationMappings[$name])) { $assoc = $this->class->associationMappings[$name]; if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) { foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { $columns[] = $sourceCol; } } } elseif ( $this->class->name !== $this->class->rootEntityName || ! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] !== $name ) { $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); $this->columnTypes[$name] = $this->class->fieldMappings[$name]['type']; } } // Add discriminator column if it is the topmost class. if ($this->class->name === $this->class->rootEntityName) { $columns[] = $this->class->getDiscriminatorColumn()['name']; } return $columns; } /** * {@inheritDoc} */ protected function assignDefaultVersionAndUpsertableValues($entity, array $id) { $values = $this->fetchVersionAndNotUpsertableValues($this->getVersionedClassMetadata(), $id); foreach ($values as $field => $value) { $value = Type::getType($this->class->fieldMappings[$field]['type'])->convertToPHPValue($value, $this->platform); $this->class->setFieldValue($entity, $field, $value); } } private function getJoinSql(string $baseTableAlias): string { $joinSql = ''; $identifierColumn = $this->class->getIdentifierColumnNames(); // INNER JOIN parent tables foreach ($this->class->parentClasses as $parentClassName) { $conditions = []; $parentClass = $this->em->getClassMetadata($parentClassName); $tableAlias = $this->getSQLTableAlias($parentClassName); $joinSql .= ' INNER JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumn as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } // OUTER JOIN sub tables foreach ($this->class->subClasses as $subClassName) { $conditions = []; $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClassName); $joinSql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; foreach ($identifierColumn as $idColumn) { $conditions[] = $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn; } $joinSql .= implode(' AND ', $conditions); } return $joinSql; } } orm/lib/Doctrine/ORM/Persisters/Entity/SingleTablePersister.php 0000644 00000013224 15120025735 0020537 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Entity; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Utility\PersisterHelper; use function array_flip; use function array_intersect; use function array_map; use function array_unshift; use function implode; /** * Persister for entities that participate in a hierarchy mapped with the * SINGLE_TABLE strategy. * * @link https://martinfowler.com/eaaCatalog/singleTableInheritance.html */ class SingleTablePersister extends AbstractEntityInheritancePersister { use SQLResultCasing; /** * {@inheritDoc} */ protected function getDiscriminatorColumnTableName() { return $this->class->getTableName(); } /** * {@inheritDoc} */ protected function getSelectColumnsSQL() { if ($this->currentPersisterContext->selectColumnListSql !== null) { return $this->currentPersisterContext->selectColumnListSql; } $columnList[] = parent::getSelectColumnsSQL(); $rootClass = $this->em->getClassMetadata($this->class->rootEntityName); $tableAlias = $this->getSQLTableAlias($rootClass->name); // Append discriminator column $discrColumn = $this->class->getDiscriminatorColumn(); $discrColumnName = $discrColumn['name']; $discrColumnType = $discrColumn['type']; $columnList[] = $tableAlias . '.' . $discrColumnName; $resultColumnName = $this->getSQLResultCasing($this->platform, $discrColumnName); $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName); $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType); // Append subclass columns foreach ($this->class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); // Regular columns foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited'])) { continue; } $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass); } // Foreign key columns foreach ($subClass->associationMappings as $assoc) { if (! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE) || isset($assoc['inherited'])) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($assoc['joinColumns'] as $joinColumn) { $columnList[] = $this->getSelectJoinColumnSQL( $tableAlias, $joinColumn['name'], $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform), PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em) ); } } } $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); return $this->currentPersisterContext->selectColumnListSql; } /** * {@inheritDoc} */ protected function getInsertColumnList() { $columns = parent::getInsertColumnList(); // Add discriminator column to the INSERT SQL $columns[] = $this->class->getDiscriminatorColumn()['name']; return $columns; } /** * {@inheritDoc} */ protected function getSQLTableAlias($className, $assocName = '') { return parent::getSQLTableAlias($this->class->rootEntityName, $assocName); } /** * {@inheritDoc} */ protected function getSelectConditionSQL(array $criteria, $assoc = null) { $conditionSql = parent::getSelectConditionSQL($criteria, $assoc); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); } /** * {@inheritDoc} */ protected function getSelectConditionCriteriaSQL(Criteria $criteria) { $conditionSql = parent::getSelectConditionCriteriaSQL($criteria); if ($conditionSql) { $conditionSql .= ' AND '; } return $conditionSql . $this->getSelectConditionDiscriminatorValueSQL(); } /** @return string */ protected function getSelectConditionDiscriminatorValueSQL() { $values = array_map( [$this->conn, 'quote'], array_flip(array_intersect($this->class->discriminatorMap, $this->class->subClasses)) ); if ($this->class->discriminatorValue !== null) { // discriminators can be 0 array_unshift($values, $this->conn->quote($this->class->discriminatorValue)); } $discColumnName = $this->class->getDiscriminatorColumn()['name']; $values = implode(', ', $values); $tableAlias = $this->getSQLTableAlias($this->class->name); return $tableAlias . '.' . $discColumnName . ' IN (' . $values . ')'; } /** * {@inheritDoc} */ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) { // Ensure that the filters are applied to the root entity of the inheritance tree $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); // we don't care about the $targetTableAlias, in a STI there is only one table. return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias); } } orm/lib/Doctrine/ORM/Persisters/Exception/CantUseInOperatorOnCompositeKeys.php 0000644 00000000523 15120025735 0023506 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Exception; use Doctrine\ORM\Exception\PersisterException; class CantUseInOperatorOnCompositeKeys extends PersisterException { public static function create(): self { return new self("Can't use IN operator on entities that have composite keys."); } } orm/lib/Doctrine/ORM/Persisters/Exception/InvalidOrientation.php 0000644 00000000600 15120025735 0020723 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Exception; use Doctrine\ORM\Exception\PersisterException; class InvalidOrientation extends PersisterException { public static function fromClassNameAndField(string $className, string $field): self { return new self('Invalid order by orientation specified for ' . $className . '#' . $field); } } orm/lib/Doctrine/ORM/Persisters/Exception/UnrecognizedField.php 0000644 00000001200 15120025735 0020516 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters\Exception; use Doctrine\ORM\Exception\PersisterException; use function sprintf; final class UnrecognizedField extends PersisterException { /** @deprecated Use {@see byFullyQualifiedName()} instead. */ public static function byName(string $field): self { return new self(sprintf('Unrecognized field: %s', $field)); } /** @param class-string $className */ public static function byFullyQualifiedName(string $className, string $field): self { return new self(sprintf('Unrecognized field: %s::$%s', $className, $field)); } } orm/lib/Doctrine/ORM/Persisters/MatchingAssociationFieldRequiresObject.php 0000644 00000001161 15120025735 0022730 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Exception\PersisterException; use function sprintf; final class MatchingAssociationFieldRequiresObject extends PersisterException { public static function fromClassAndAssociation(string $class, string $associationName): self { return new self(sprintf( 'Cannot match on %s::%s with a non-object value. Matching objects by id is ' . 'not compatible with matching on an in-memory collection, which compares objects by reference.', $class, $associationName )); } } orm/lib/Doctrine/ORM/Persisters/PersisterException.php 0000644 00000001300 15120025735 0017020 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters; use Doctrine\ORM\Exception\ORMException; use function sprintf; class PersisterException extends ORMException { /** * @param string $class * @param string $associationName * * @return PersisterException */ public static function matchingAssocationFieldRequiresObject($class, $associationName) { return new self(sprintf( 'Cannot match on %s::%s with a non-object value. Matching objects by id is ' . 'not compatible with matching on an in-memory collection, which compares objects by reference.', $class, $associationName )); } } orm/lib/Doctrine/ORM/Persisters/SqlExpressionVisitor.php 0000644 00000006020 15120025735 0017364 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Value; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\Entity\BasicEntityPersister; use RuntimeException; use function defined; use function implode; use function in_array; use function is_object; /** * Visit Expressions and generate SQL WHERE conditions from them. */ class SqlExpressionVisitor extends ExpressionVisitor { /** @var BasicEntityPersister */ private $persister; /** @var ClassMetadata */ private $classMetadata; public function __construct(BasicEntityPersister $persister, ClassMetadata $classMetadata) { $this->persister = $persister; $this->classMetadata = $classMetadata; } /** * Converts a comparison expression into the target query language output. * * @return mixed */ public function walkComparison(Comparison $comparison) { $field = $comparison->getField(); $value = $comparison->getValue()->getValue(); // shortcut for walkValue() if ( isset($this->classMetadata->associationMappings[$field]) && $value !== null && ! is_object($value) && ! in_array($comparison->getOperator(), [Comparison::IN, Comparison::NIN], true) ) { throw MatchingAssociationFieldRequiresObject::fromClassAndAssociation( $this->classMetadata->name, $field ); } return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator()); } /** * Converts a composite expression into the target query language output. * * @return string * * @throws RuntimeException */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = []; foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch ($expr->getType()) { case CompositeExpression::TYPE_AND: return '(' . implode(' AND ', $expressionList) . ')'; case CompositeExpression::TYPE_OR: return '(' . implode(' OR ', $expressionList) . ')'; default: // Multiversion support for `doctrine/collections` before and after v2.1.0 if (defined(CompositeExpression::class . '::TYPE_NOT') && $expr->getType() === CompositeExpression::TYPE_NOT) { return 'NOT (' . $expressionList[0] . ')'; } throw new RuntimeException('Unknown composite ' . $expr->getType()); } } /** * Converts a value expression into the target query language part. * * @return string */ public function walkValue(Value $value) { return '?'; } } orm/lib/Doctrine/ORM/Persisters/SqlValueVisitor.php 0000644 00000004547 15120025735 0016315 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Persisters; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Value; /** * Extract the values from a criteria/expression */ class SqlValueVisitor extends ExpressionVisitor { /** @var mixed[] */ private $values = []; /** @var mixed[][] */ private $types = []; /** * Converts a comparison expression into the target query language output. * * {@inheritDoc} */ public function walkComparison(Comparison $comparison) { $value = $this->getValueFromComparison($comparison); $this->values[] = $value; $this->types[] = [$comparison->getField(), $value, $comparison->getOperator()]; return null; } /** * Converts a composite expression into the target query language output. * * {@inheritDoc} */ public function walkCompositeExpression(CompositeExpression $expr) { foreach ($expr->getExpressionList() as $child) { $this->dispatch($child); } return null; } /** * Converts a value expression into the target query language part. * * {@inheritDoc} */ public function walkValue(Value $value) { return null; } /** * Returns the Parameters and Types necessary for matching the last visited expression. * * @return mixed[][] * @psalm-return array{0: array, 1: array<array<mixed>>} */ public function getParamsAndTypes() { return [$this->values, $this->types]; } /** * Returns the value from a Comparison. In case of a CONTAINS comparison, * the value is wrapped in %-signs, because it will be used in a LIKE clause. * * @return mixed */ protected function getValueFromComparison(Comparison $comparison) { $value = $comparison->getValue()->getValue(); switch ($comparison->getOperator()) { case Comparison::CONTAINS: return '%' . $value . '%'; case Comparison::STARTS_WITH: return $value . '%'; case Comparison::ENDS_WITH: return '%' . $value; default: return $value; } } } orm/lib/Doctrine/ORM/Proxy/Autoloader.php 0000644 00000000346 15120025735 0014247 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Proxy; use Doctrine\Common\Proxy\Autoloader as BaseAutoloader; /** @deprecated use \Doctrine\Common\Proxy\Autoloader instead */ class Autoloader extends BaseAutoloader { } orm/lib/Doctrine/ORM/Proxy/Proxy.php 0000644 00000000375 15120025735 0013273 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Proxy; use Doctrine\Common\Proxy\Proxy as BaseProxy; /** * Interface for proxy classes. * * @deprecated 2.14. Use \Doctrine\Persistence\Proxy instead */ interface Proxy extends BaseProxy { } orm/lib/Doctrine/ORM/Proxy/ProxyFactory.php 0000644 00000032705 15120025735 0014625 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Proxy; use Closure; use Doctrine\Common\Proxy\AbstractProxyFactory; use Doctrine\Common\Proxy\Proxy as CommonProxy; use Doctrine\Common\Proxy\ProxyDefinition; use Doctrine\Common\Proxy\ProxyGenerator; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\Persisters\Entity\EntityPersister; use Doctrine\ORM\Proxy\Proxy as LegacyProxy; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Utility\IdentifierFlattener; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Proxy; use ReflectionProperty; use Symfony\Component\VarExporter\ProxyHelper; use Symfony\Component\VarExporter\VarExporter; use Throwable; use function array_flip; use function str_replace; use function strpos; use function substr; use function uksort; /** * This factory is used to create proxy objects for entities at runtime. * * @psalm-type AutogenerateMode = ProxyFactory::AUTOGENERATE_NEVER|ProxyFactory::AUTOGENERATE_ALWAYS|ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS|ProxyFactory::AUTOGENERATE_EVAL|ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED */ class ProxyFactory extends AbstractProxyFactory { private const PROXY_CLASS_TEMPLATE = <<<'EOPHP' <?php namespace <namespace>; /** * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR */ class <proxyShortClassName> extends \<className> implements \<baseProxyInterface> { <useLazyGhostTrait> /** * @internal */ public bool $__isCloning = false; public function __construct(?\Closure $initializer = null) { self::createLazyGhost($initializer, <skippedProperties>, $this); } public function __isInitialized(): bool { return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); } public function __clone() { $this->__isCloning = true; try { $this->__doClone(); } finally { $this->__isCloning = false; } } public function __serialize(): array { <serializeImpl> } } EOPHP; /** @var EntityManagerInterface The EntityManager this factory is bound to. */ private $em; /** @var UnitOfWork The UnitOfWork this factory uses to retrieve persisters */ private $uow; /** @var string */ private $proxyNs; /** * The IdentifierFlattener used for manipulating identifiers * * @var IdentifierFlattener */ private $identifierFlattener; /** * Initializes a new instance of the <tt>ProxyFactory</tt> class that is * connected to the given <tt>EntityManager</tt>. * * @param EntityManagerInterface $em The EntityManager the new factory works for. * @param string $proxyDir The directory to use for the proxy classes. It must exist. * @param string $proxyNs The namespace to use for the proxy classes. * @param bool|int $autoGenerate The strategy for automatically generating proxy classes. Possible * values are constants of {@see ProxyFactory::AUTOGENERATE_*}. * @psalm-param bool|AutogenerateMode $autoGenerate */ public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $autoGenerate = self::AUTOGENERATE_NEVER) { $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs); if ($em->getConfiguration()->isLazyGhostObjectEnabled()) { $proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class); $proxyGenerator->setPlaceholder('useLazyGhostTrait', Closure::fromCallable([$this, 'generateUseLazyGhostTrait'])); $proxyGenerator->setPlaceholder('skippedProperties', Closure::fromCallable([$this, 'generateSkippedProperties'])); $proxyGenerator->setPlaceholder('serializeImpl', Closure::fromCallable([$this, 'generateSerializeImpl'])); $proxyGenerator->setProxyClassTemplate(self::PROXY_CLASS_TEMPLATE); } else { $proxyGenerator->setPlaceholder('baseProxyInterface', LegacyProxy::class); } parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate); $this->em = $em; $this->uow = $em->getUnitOfWork(); $this->proxyNs = $proxyNs; $this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory()); } /** * {@inheritDoc} */ protected function skipClass(ClassMetadata $metadata) { return $metadata->isMappedSuperclass || $metadata->isEmbeddedClass || $metadata->getReflectionClass()->isAbstract(); } /** * {@inheritDoc} */ protected function createProxyDefinition($className) { $classMetadata = $this->em->getClassMetadata($className); $entityPersister = $this->uow->getEntityPersister($className); if ($this->em->getConfiguration()->isLazyGhostObjectEnabled()) { $initializer = $this->createLazyInitializer($classMetadata, $entityPersister); $cloner = static function (): void { }; } else { $initializer = $this->createInitializer($classMetadata, $entityPersister); $cloner = $this->createCloner($classMetadata, $entityPersister); } return new ProxyDefinition( ClassUtils::generateProxyClassName($className, $this->proxyNs), $classMetadata->getIdentifierFieldNames(), $classMetadata->getReflectionProperties(), $initializer, $cloner ); } /** * Creates a closure capable of initializing a proxy * * @psalm-return Closure(CommonProxy):void * * @throws EntityNotFoundException */ private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure { $wakeupProxy = $classMetadata->getReflectionClass()->hasMethod('__wakeup'); return function (CommonProxy $proxy) use ($entityPersister, $classMetadata, $wakeupProxy): void { $initializer = $proxy->__getInitializer(); $cloner = $proxy->__getCloner(); $proxy->__setInitializer(null); $proxy->__setCloner(null); if ($proxy->__isInitialized()) { return; } $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { if (! isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } $proxy->__setInitialized(true); if ($wakeupProxy) { $proxy->__wakeup(); } $identifier = $classMetadata->getIdentifierValues($proxy); try { $entity = $entityPersister->loadById($identifier, $proxy); } catch (Throwable $exception) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); throw $exception; } if ($entity === null) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); throw EntityNotFoundException::fromClassNameAndIdentifier( $classMetadata->getName(), $this->identifierFlattener->flattenIdentifier($classMetadata, $identifier) ); } }; } /** * Creates a closure capable of initializing a proxy * * @return Closure(Proxy):void * * @throws EntityNotFoundException */ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure { return function (Proxy $proxy) use ($entityPersister, $classMetadata): void { $identifier = $classMetadata->getIdentifierValues($proxy); $entity = $entityPersister->loadById($identifier, $proxy->__isCloning ? null : $proxy); if ($entity === null) { throw EntityNotFoundException::fromClassNameAndIdentifier( $classMetadata->getName(), $this->identifierFlattener->flattenIdentifier($classMetadata, $identifier) ); } if (! $proxy->__isCloning) { return; } $class = $entityPersister->getClassMetadata(); foreach ($class->getReflectionProperties() as $property) { if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) { continue; } $property->setAccessible(true); $property->setValue($proxy, $property->getValue($entity)); } }; } /** * Creates a closure capable of finalizing state a cloned proxy * * @psalm-return Closure(CommonProxy):void * * @throws EntityNotFoundException */ private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure { return function (CommonProxy $proxy) use ($entityPersister, $classMetadata): void { if ($proxy->__isInitialized()) { return; } $proxy->__setInitialized(true); $proxy->__setInitializer(null); $class = $entityPersister->getClassMetadata(); $identifier = $classMetadata->getIdentifierValues($proxy); $original = $entityPersister->loadById($identifier); if ($original === null) { throw EntityNotFoundException::fromClassNameAndIdentifier( $classMetadata->getName(), $this->identifierFlattener->flattenIdentifier($classMetadata, $identifier) ); } foreach ($class->getReflectionProperties() as $property) { if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) { continue; } $property->setAccessible(true); $property->setValue($proxy, $property->getValue($original)); } }; } private function generateUseLazyGhostTrait(ClassMetadata $class): string { $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); $code = substr($code, 7 + (int) strpos($code, "\n{")); $code = substr($code, 0, (int) strpos($code, "\n}")); $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { initializeLazyObject as __load; setLazyObjectAsInitialized as public __setInitialized; isLazyObjectInitialized as private; createLazyGhost as private; resetLazyObject as private; __clone as private __doClone; }'), $code); return $code; } private function generateSkippedProperties(ClassMetadata $class): string { $skippedProperties = ['__isCloning' => true]; $identifiers = array_flip($class->getIdentifierFieldNames()); $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; $reflector = $class->getReflectionClass(); while ($reflector) { foreach ($reflector->getProperties($filter) as $property) { $name = $property->getName(); if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { continue; } $prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : ''); $skippedProperties[$prefix . $name] = true; } $filter = ReflectionProperty::IS_PRIVATE; $reflector = $reflector->getParentClass(); } uksort($skippedProperties, 'strnatcmp'); $code = VarExporter::export($skippedProperties); $code = str_replace(VarExporter::export($class->getName()), 'parent::class', $code); $code = str_replace("\n", "\n ", $code); return $code; } private function generateSerializeImpl(ClassMetadata $class): string { $reflector = $class->getReflectionClass(); $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; $code = '$properties = ' . $properties . '; unset($properties["\0" . self::class . "\0lazyObjectState"], $properties[\'__isCloning\']); '; if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { return $code . 'return $properties;'; } return $code . '$data = []; foreach (parent::__sleep() as $name) { $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->getName() . '\0$name"] ?? $k = null; if (null === $k) { trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); } else { $data[$k] = $value; } } return $data;'; } } orm/lib/Doctrine/ORM/Query/AST/ExistsExpression.php 0000644 00000001175 15120025735 0016123 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * @link www.doctrine-project.org */ class ExistsExpression extends Node { /** @var bool */ public $not; /** @var Subselect */ public $subselect; /** @param Subselect $subselect */ public function __construct($subselect, bool $not = false) { $this->subselect = $subselect; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkExistsExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/FromClause.php 0000644 00000001302 15120025735 0014614 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} * * @link www.doctrine-project.org */ class FromClause extends Node { /** @var mixed[] */ public $identificationVariableDeclarations = []; /** @param mixed[] $identificationVariableDeclarations */ public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkFromClause($this); } } orm/lib/Doctrine/ORM/Query/AST/GroupByClause.php 0000644 00000000707 15120025735 0015310 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class GroupByClause extends Node { /** @var mixed[] */ public $groupByItems = []; /** @param mixed[] $groupByItems */ public function __construct(array $groupByItems) { $this->groupByItems = $groupByItems; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkGroupByClause($this); } } orm/lib/Doctrine/ORM/Query/AST/IdentificationVariableDeclaration.php 0000644 00000002062 15120025735 0021325 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* * * @link www.doctrine-project.org */ class IdentificationVariableDeclaration extends Node { /** @var RangeVariableDeclaration|null */ public $rangeVariableDeclaration = null; /** @var IndexBy|null */ public $indexBy = null; /** @var mixed[] */ public $joins = []; /** * @param RangeVariableDeclaration|null $rangeVariableDecl * @param IndexBy|null $indexBy * @param mixed[] $joins */ public function __construct($rangeVariableDecl, $indexBy, array $joins) { $this->rangeVariableDeclaration = $rangeVariableDecl; $this->indexBy = $indexBy; $this->joins = $joins; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkIdentificationVariableDeclaration($this); } } orm/lib/Doctrine/ORM/Query/AST/InListExpression.php 0000644 00000000705 15120025735 0016044 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class InListExpression extends InExpression { /** @var non-empty-list<mixed> */ public $literals; /** @param non-empty-list<mixed> $literals */ public function __construct(ArithmeticExpression $expression, array $literals, bool $not = false) { $this->literals = $literals; $this->not = $not; parent::__construct($expression); } } orm/lib/Doctrine/ORM/Query/AST/IndexBy.php 0000644 00000001345 15120025735 0014125 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression * * @link www.doctrine-project.org */ class IndexBy extends Node { /** @var PathExpression */ public $singleValuedPathExpression = null; /** * @deprecated * * @var PathExpression */ public $simpleStateFieldPathExpression = null; public function __construct(PathExpression $singleValuedPathExpression) { $this->singleValuedPathExpression = $this->simpleStateFieldPathExpression = $singleValuedPathExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkIndexBy($this); } } orm/lib/Doctrine/ORM/Query/AST/InstanceOfExpression.php 0000644 00000002654 15120025735 0016700 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\Deprecations\Deprecation; use function func_num_args; /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * InstanceOfParameter ::= AbstractSchemaName | InputParameter * * @link www.doctrine-project.org */ class InstanceOfExpression extends Node { /** @var bool */ public $not; /** @var string */ public $identificationVariable; /** @var non-empty-list<InputParameter|string> */ public $value; /** * @param string $identVariable * @param non-empty-list<InputParameter|string> $value */ public function __construct($identVariable, array $value = [], bool $not = false) { if (func_num_args() < 2) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10267', 'Not passing a value for $value to %s() is deprecated.', __METHOD__ ); } $this->identificationVariable = $identVariable; $this->value = $value; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkInstanceOfExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/JoinAssociationDeclaration.php 0000644 00000002226 15120025735 0020024 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org */ class JoinAssociationDeclaration extends Node { /** @var JoinAssociationPathExpression */ public $joinAssociationPathExpression; /** @var string */ public $aliasIdentificationVariable; /** @var IndexBy|null */ public $indexBy; /** * @param JoinAssociationPathExpression $joinAssociationPathExpression * @param string $aliasIdentificationVariable * @param IndexBy|null $indexBy */ public function __construct($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy) { $this->joinAssociationPathExpression = $joinAssociationPathExpression; $this->aliasIdentificationVariable = $aliasIdentificationVariable; $this->indexBy = $indexBy; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkJoinAssociationDeclaration($this); } } orm/lib/Doctrine/ORM/Query/AST/Functions/AbsFunction.php 0000644 00000001716 15120025735 0016750 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "ABS" "(" SimpleArithmeticExpression ")" * * @link www.doctrine-project.org */ class AbsFunction extends FunctionNode { /** @var SimpleArithmeticExpression */ public $simpleArithmeticExpression; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return 'ABS(' . $sqlWalker->walkSimpleArithmeticExpression( $this->simpleArithmeticExpression ) . ')'; } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/AvgFunction.php 0000644 00000001164 15120025735 0016755 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "AVG" "(" ["DISTINCT"] StringPrimary ")" */ final class AvgFunction extends FunctionNode { /** @var AggregateExpression */ private $aggregateExpression; public function getSql(SqlWalker $sqlWalker): string { return $this->aggregateExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void { $this->aggregateExpression = $parser->AggregateExpression(); } } orm/lib/Doctrine/ORM/Query/AST/Functions/BitAndFunction.php 0000644 00000002261 15120025735 0017400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * @link www.doctrine-project.org */ class BitAndFunction extends FunctionNode { /** @var Node */ public $firstArithmetic; /** @var Node */ public $secondArithmetic; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitAndComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/BitOrFunction.php 0000644 00000002256 15120025735 0017262 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * @link www.doctrine-project.org */ class BitOrFunction extends FunctionNode { /** @var Node */ public $firstArithmetic; /** @var Node */ public $secondArithmetic; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); return $platform->getBitOrComparisonExpression( $this->firstArithmetic->dispatch($sqlWalker), $this->secondArithmetic->dispatch($sqlWalker) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->secondArithmetic = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/ConcatFunction.php 0000644 00000003150 15120025735 0017444 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")" * * @link www.doctrine-project.org */ class ConcatFunction extends FunctionNode { /** @var Node */ public $firstStringPrimary; /** @var Node */ public $secondStringPrimary; /** @psalm-var list<Node> */ public $concatExpressions = []; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); $args = []; foreach ($this->concatExpressions as $expression) { $args[] = $sqlWalker->walkStringPrimary($expression); } return $platform->getConcatExpression(...$args); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $this->concatExpressions[] = $this->firstStringPrimary; $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $this->concatExpressions[] = $this->secondStringPrimary; while ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->concatExpressions[] = $parser->StringPrimary(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/CountFunction.php 0000644 00000001541 15120025735 0017327 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\AST\TypedExpression; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "COUNT" "(" ["DISTINCT"] StringPrimary ")" */ final class CountFunction extends FunctionNode implements TypedExpression { /** @var AggregateExpression */ private $aggregateExpression; public function getSql(SqlWalker $sqlWalker): string { return $this->aggregateExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void { $this->aggregateExpression = $parser->AggregateExpression(); } public function getReturnType(): Type { return Type::getType(Types::INTEGER); } } orm/lib/Doctrine/ORM/Query/AST/Functions/CurrentDateFunction.php 0000644 00000001261 15120025735 0020456 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "CURRENT_DATE" * * @link www.doctrine-project.org */ class CurrentDateFunction extends FunctionNode { /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentDateSQL(); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/CurrentTimeFunction.php 0000644 00000001261 15120025735 0020477 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "CURRENT_TIME" * * @link www.doctrine-project.org */ class CurrentTimeFunction extends FunctionNode { /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimeSQL(); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/CurrentTimestampFunction.php 0000644 00000001300 15120025735 0021536 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "CURRENT_TIMESTAMP" * * @link www.doctrine-project.org */ class CurrentTimestampFunction extends FunctionNode { /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getCurrentTimestampSQL(); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php 0000644 00000006600 15120025735 0017526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\SqlWalker; use function strtolower; /** * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" * * @link www.doctrine-project.org */ class DateAddFunction extends FunctionNode { /** @var Node */ public $firstDateExpression = null; /** @var Node */ public $intervalExpression = null; /** @var Node */ public $unit = null; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'second': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddSecondsExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'minute': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMinutesExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'hour': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddHourExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'week': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddWeeksExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'year': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddYearsExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_ADD() only supports units of type second, minute, hour, day, week, month and year.' ); } } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstDateExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->intervalExpression = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->unit = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/DateDiffFunction.php 0000644 00000002116 15120025735 0017704 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * @link www.doctrine-project.org */ class DateDiffFunction extends FunctionNode { /** @var Node */ public $date1; /** @var Node */ public $date2; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression( $this->date1->dispatch($sqlWalker), $this->date2->dispatch($sqlWalker) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->date1 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_COMMA); $this->date2 = $parser->ArithmeticPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php 0000644 00000005147 15120025735 0017574 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\SqlWalker; use function strtolower; /** * "DATE_SUB(date1, interval, unit)" * * @link www.doctrine-project.org */ class DateSubFunction extends DateAddFunction { /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { switch (strtolower($this->unit->value)) { case 'second': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubSecondsExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'minute': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMinutesExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'hour': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubHourExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'day': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'week': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubWeeksExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'month': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); case 'year': return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubYearsExpression( $this->firstDateExpression->dispatch($sqlWalker), $this->intervalExpression->dispatch($sqlWalker) ); default: throw QueryException::semanticalError( 'DATE_SUB() only supports units of type second, minute, hour, day, week, month and year.' ); } } } orm/lib/Doctrine/ORM/Query/AST/Functions/FunctionNode.php 0000644 00000001477 15120025735 0017134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * Abstract Function Node. * * @link www.doctrine-project.org * * @psalm-consistent-constructor */ abstract class FunctionNode extends Node { /** @var string */ public $name; /** @param string $name */ public function __construct($name) { $this->name = $name; } /** @return string */ abstract public function getSql(SqlWalker $sqlWalker); /** * @param SqlWalker $sqlWalker * * @return string */ public function dispatch($sqlWalker) { return $sqlWalker->walkFunction($this); } /** @return void */ abstract public function parse(Parser $parser); } orm/lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php 0000644 00000006174 15120025735 0020037 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\SqlWalker; use function assert; use function reset; use function sprintf; /** * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" * * @link www.doctrine-project.org */ class IdentityFunction extends FunctionNode { /** @var PathExpression */ public $pathExpression; /** @var string|null */ public $fieldMapping; /** * {@inheritDoc} */ public function getSql(SqlWalker $sqlWalker) { assert($this->pathExpression->field !== null); $entityManager = $sqlWalker->getEntityManager(); $platform = $entityManager->getConnection()->getDatabasePlatform(); $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy(); $dqlAlias = $this->pathExpression->identificationVariable; $assocField = $this->pathExpression->field; $assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField]; $targetEntity = $entityManager->getClassMetadata($assoc['targetEntity']); $joinColumn = reset($assoc['joinColumns']); if ($this->fieldMapping !== null) { if (! isset($targetEntity->fieldMappings[$this->fieldMapping])) { throw new QueryException(sprintf('Undefined reference field mapping "%s"', $this->fieldMapping)); } $field = $targetEntity->fieldMappings[$this->fieldMapping]; $joinColumn = null; foreach ($assoc['joinColumns'] as $mapping) { if ($mapping['referencedColumnName'] === $field['columnName']) { $joinColumn = $mapping; break; } } if ($joinColumn === null) { throw new QueryException(sprintf('Unable to resolve the reference field mapping "%s"', $this->fieldMapping)); } } // The table with the relation may be a subclass, so get the table name from the association definition $tableName = $entityManager->getClassMetadata($assoc['sourceEntity'])->getTableName(); $tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias); $columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform); return $tableAlias . '.' . $columnName; } /** * {@inheritDoc} */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->pathExpression = $parser->SingleValuedAssociationPathExpression(); if ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $parser->match(Lexer::T_STRING); $token = $parser->getLexer()->token; assert($token !== null); $this->fieldMapping = $token->value; } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/LengthFunction.php 0000644 00000002212 15120025735 0017454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\TypedExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "LENGTH" "(" StringPrimary ")" * * @link www.doctrine-project.org */ class LengthFunction extends FunctionNode implements TypedExpression { /** @var Node */ public $stringPrimary; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression( $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } public function getReturnType(): Type { return Type::getType(Types::INTEGER); } } orm/lib/Doctrine/ORM/Query/AST/Functions/LocateFunction.php 0000644 00000003641 15120025735 0017451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" * * @link www.doctrine-project.org */ class LocateFunction extends FunctionNode { /** @var Node */ public $firstStringPrimary; /** @var Node */ public $secondStringPrimary; /** @var SimpleArithmeticExpression|bool */ public $simpleArithmeticExpression = false; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); $firstString = $sqlWalker->walkStringPrimary($this->firstStringPrimary); $secondString = $sqlWalker->walkStringPrimary($this->secondStringPrimary); if ($this->simpleArithmeticExpression) { return $platform->getLocateExpression( $secondString, $firstString, $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) ); } return $platform->getLocateExpression($secondString, $firstString); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstStringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->secondStringPrimary = $parser->StringPrimary(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/LowerFunction.php 0000644 00000001617 15120025735 0017333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use function sprintf; /** * "LOWER" "(" StringPrimary ")" * * @link www.doctrine-project.org */ class LowerFunction extends FunctionNode { /** @var Node */ public $stringPrimary; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return sprintf( 'LOWER(%s)', $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/MaxFunction.php 0000644 00000001164 15120025735 0016765 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "MAX" "(" ["DISTINCT"] StringPrimary ")" */ final class MaxFunction extends FunctionNode { /** @var AggregateExpression */ private $aggregateExpression; public function getSql(SqlWalker $sqlWalker): string { return $this->aggregateExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void { $this->aggregateExpression = $parser->AggregateExpression(); } } orm/lib/Doctrine/ORM/Query/AST/Functions/MinFunction.php 0000644 00000001164 15120025735 0016763 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "MIN" "(" ["DISTINCT"] StringPrimary ")" */ final class MinFunction extends FunctionNode { /** @var AggregateExpression */ private $aggregateExpression; public function getSql(SqlWalker $sqlWalker): string { return $this->aggregateExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void { $this->aggregateExpression = $parser->AggregateExpression(); } } orm/lib/Doctrine/ORM/Query/AST/Functions/ModFunction.php 0000644 00000002561 15120025735 0016761 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * @link www.doctrine-project.org */ class ModFunction extends FunctionNode { /** @var SimpleArithmeticExpression */ public $firstSimpleArithmeticExpression; /** @var SimpleArithmeticExpression */ public $secondSimpleArithmeticExpression; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression( $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/SizeFunction.php 0000644 00000010063 15120025735 0017150 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use function assert; /** * "SIZE" "(" CollectionValuedPathExpression ")" * * @link www.doctrine-project.org */ class SizeFunction extends FunctionNode { /** @var PathExpression */ public $collectionPathExpression; /** * @inheritDoc * @todo If the collection being counted is already joined, the SQL can be simpler (more efficient). */ public function getSql(SqlWalker $sqlWalker) { assert($this->collectionPathExpression->field !== null); $entityManager = $sqlWalker->getEntityManager(); $platform = $entityManager->getConnection()->getDatabasePlatform(); $quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy(); $dqlAlias = $this->collectionPathExpression->identificationVariable; $assocField = $this->collectionPathExpression->field; $class = $sqlWalker->getMetadataForDqlAlias($dqlAlias); $assoc = $class->associationMappings[$assocField]; $sql = 'SELECT COUNT(*) FROM '; if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) { $targetClass = $entityManager->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $quoteStrategy->getTableName($targetClass, $platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $first = true; foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { if ($first) { $first = false; } else { $sql .= ' AND '; } $sql .= $targetTableAlias . '.' . $sourceColumn . ' = ' . $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform); } } else { // many-to-many $targetClass = $entityManager->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']); $sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias); // join to target table $sql .= $quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $platform) . ' ' . $joinTableAlias . ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $first = true; foreach ($joinColumns as $joinColumn) { if ($first) { $first = false; } else { $sql .= ' AND '; } $sourceColumnName = $quoteStrategy->getColumnName( $class->fieldNames[$joinColumn['referencedColumnName']], $class, $platform ); $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $sourceColumnName; } } return '(' . $sql . ')'; } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->collectionPathExpression = $parser->CollectionValuedPathExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/SqrtFunction.php 0000644 00000001771 15120025735 0017175 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use function sprintf; /** * "SQRT" "(" SimpleArithmeticExpression ")" * * @link www.doctrine-project.org */ class SqrtFunction extends FunctionNode { /** @var SimpleArithmeticExpression */ public $simpleArithmeticExpression; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return sprintf( 'SQRT(%s)', $sqlWalker->walkSimpleArithmeticExpression($this->simpleArithmeticExpression) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/SubstringFunction.php 0000644 00000003752 15120025735 0020225 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" * * @link www.doctrine-project.org */ class SubstringFunction extends FunctionNode { /** @var Node */ public $stringPrimary; /** @var SimpleArithmeticExpression */ public $firstSimpleArithmeticExpression; /** @var SimpleArithmeticExpression|null */ public $secondSimpleArithmeticExpression = null; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { $optionalSecondSimpleArithmeticExpression = null; if ($this->secondSimpleArithmeticExpression !== null) { $optionalSecondSimpleArithmeticExpression = $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression); } return $sqlWalker->getConnection()->getDatabasePlatform()->getSubstringExpression( $sqlWalker->walkStringPrimary($this->stringPrimary), $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression), $optionalSecondSimpleArithmeticExpression ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_COMMA); $this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); $lexer = $parser->getLexer(); if ($lexer->isNextToken(Lexer::T_COMMA)) { $parser->match(Lexer::T_COMMA); $this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression(); } $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/Functions/SumFunction.php 0000644 00000001164 15120025735 0017004 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; /** * "SUM" "(" ["DISTINCT"] StringPrimary ")" */ final class SumFunction extends FunctionNode { /** @var AggregateExpression */ private $aggregateExpression; public function getSql(SqlWalker $sqlWalker): string { return $this->aggregateExpression->dispatch($sqlWalker); } public function parse(Parser $parser): void { $this->aggregateExpression = $parser->AggregateExpression(); } } orm/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php 0000644 00000006006 15120025735 0017153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\DBAL\Platforms\TrimMode; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use function assert; use function strcasecmp; /** * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" * * @link www.doctrine-project.org */ class TrimFunction extends FunctionNode { /** @var bool */ public $leading; /** @var bool */ public $trailing; /** @var bool */ public $both; /** @var string|false */ public $trimChar = false; /** @var Node */ public $stringPrimary; /** * {@inheritDoc} */ public function getSql(SqlWalker $sqlWalker) { $stringPrimary = $sqlWalker->walkStringPrimary($this->stringPrimary); $platform = $sqlWalker->getConnection()->getDatabasePlatform(); $trimMode = $this->getTrimMode(); if ($this->trimChar !== false) { return $platform->getTrimExpression( $stringPrimary, $trimMode, $platform->quoteStringLiteral($this->trimChar) ); } return $platform->getTrimExpression($stringPrimary, $trimMode); } /** * {@inheritDoc} */ public function parse(Parser $parser) { $lexer = $parser->getLexer(); $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->parseTrimMode($parser); if ($lexer->isNextToken(Lexer::T_STRING)) { $parser->match(Lexer::T_STRING); assert($lexer->token !== null); $this->trimChar = $lexer->token->value; } if ($this->leading || $this->trailing || $this->both || $this->trimChar) { $parser->match(Lexer::T_FROM); } $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } /** @psalm-return TrimMode::* */ private function getTrimMode(): int { if ($this->leading) { return TrimMode::LEADING; } if ($this->trailing) { return TrimMode::TRAILING; } if ($this->both) { return TrimMode::BOTH; } return TrimMode::UNSPECIFIED; } private function parseTrimMode(Parser $parser): void { $lexer = $parser->getLexer(); assert($lexer->lookahead !== null); $value = $lexer->lookahead->value; if (strcasecmp('leading', $value) === 0) { $parser->match(Lexer::T_LEADING); $this->leading = true; return; } if (strcasecmp('trailing', $value) === 0) { $parser->match(Lexer::T_TRAILING); $this->trailing = true; return; } if (strcasecmp('both', $value) === 0) { $parser->match(Lexer::T_BOTH); $this->both = true; return; } } } orm/lib/Doctrine/ORM/Query/AST/Functions/UpperFunction.php 0000644 00000001617 15120025735 0017336 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST\Functions; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use function sprintf; /** * "UPPER" "(" StringPrimary ")" * * @link www.doctrine-project.org */ class UpperFunction extends FunctionNode { /** @var Node */ public $stringPrimary; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return sprintf( 'UPPER(%s)', $sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary) ); } /** @inheritDoc */ public function parse(Parser $parser) { $parser->match(Lexer::T_IDENTIFIER); $parser->match(Lexer::T_OPEN_PARENTHESIS); $this->stringPrimary = $parser->StringPrimary(); $parser->match(Lexer::T_CLOSE_PARENTHESIS); } } orm/lib/Doctrine/ORM/Query/AST/JoinAssociationPathExpression.php 0000644 00000001325 15120025735 0020552 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * JoinAssociationPathExpression ::= IdentificationVariable "." (SingleValuedAssociationField | CollectionValuedAssociationField) * * @link www.doctrine-project.org */ class JoinAssociationPathExpression extends Node { /** @var string */ public $identificationVariable; /** @var string */ public $associationField; /** * @param string $identificationVariable * @param string $associationField */ public function __construct($identificationVariable, $associationField) { $this->identificationVariable = $identificationVariable; $this->associationField = $associationField; } } orm/lib/Doctrine/ORM/Query/AST/JoinClassPathExpression.php 0000644 00000001504 15120025735 0017342 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * JoinClassPathExpression ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org */ class JoinClassPathExpression extends Node { /** @var mixed */ public $abstractSchemaName; /** @var mixed */ public $aliasIdentificationVariable; /** * @param mixed $abstractSchemaName * @param mixed $aliasIdentificationVar */ public function __construct($abstractSchemaName, $aliasIdentificationVar) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkJoinPathExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/JoinVariableDeclaration.php 0000644 00000001225 15120025735 0017273 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * JoinVariableDeclaration ::= Join [IndexBy] * * @link www.doctrine-project.org */ class JoinVariableDeclaration extends Node { /** @var Join */ public $join; /** @var IndexBy|null */ public $indexBy; /** * @param Join $join * @param IndexBy|null $indexBy */ public function __construct($join, $indexBy) { $this->join = $join; $this->indexBy = $indexBy; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkJoinVariableDeclaration($this); } } orm/lib/Doctrine/ORM/Query/AST/LikeExpression.php 0000644 00000002337 15120025735 0015531 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\AST\Functions\FunctionNode; /** * LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char] * * @link www.doctrine-project.org */ class LikeExpression extends Node { /** @var bool */ public $not = false; /** @var Node|string */ public $stringExpression; /** @var InputParameter|FunctionNode|PathExpression|Literal */ public $stringPattern; /** @var Literal|null */ public $escapeChar; /** * @param Node|string $stringExpression * @param InputParameter|FunctionNode|PathExpression|Literal $stringPattern * @param Literal|null $escapeChar */ public function __construct($stringExpression, $stringPattern, $escapeChar = null, bool $not = false) { $this->stringExpression = $stringExpression; $this->stringPattern = $stringPattern; $this->escapeChar = $escapeChar; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkLikeExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/Literal.php 0000644 00000001230 15120025735 0014150 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class Literal extends Node { public const STRING = 1; public const BOOLEAN = 2; public const NUMERIC = 3; /** * @var int * @psalm-var self::* */ public $type; /** @var mixed */ public $value; /** * @param int $type * @param mixed $value * @psalm-param self::* $type */ public function __construct($type, $value) { $this->type = $type; $this->value = $value; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkLiteral($this); } } orm/lib/Doctrine/ORM/Query/AST/NewObjectExpression.php 0000644 00000001301 15120025735 0016513 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")" * * @link www.doctrine-project.org */ class NewObjectExpression extends Node { /** @var string */ public $className; /** @var mixed[] */ public $args; /** * @param string $className * @param mixed[] $args */ public function __construct($className, array $args) { $this->className = $className; $this->args = $args; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNewObject($this); } } orm/lib/Doctrine/ORM/Query/AST/Node.php 0000644 00000004244 15120025735 0013451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\SqlWalker; use function get_debug_type; use function get_object_vars; use function is_array; use function is_object; use function str_repeat; use function var_export; use const PHP_EOL; /** * Abstract class of an AST node. * * @link www.doctrine-project.org */ abstract class Node { /** * Double-dispatch method, supposed to dispatch back to the walker. * * Implementation is not mandatory for all nodes. * * @param SqlWalker $walker * * @return string * * @throws ASTException */ public function dispatch($walker) { throw ASTException::noDispatchForNode($this); } /** * Dumps the AST Node into a string representation for information purpose only. * * @return string */ public function __toString() { return $this->dump($this); } /** * @param mixed $value * * @return string */ public function dump($value) { static $ident = 0; $str = ''; if ($value instanceof Node) { $str .= get_debug_type($value) . '(' . PHP_EOL; $props = get_object_vars($value); foreach ($props as $name => $prop) { $ident += 4; $str .= str_repeat(' ', $ident) . '"' . $name . '": ' . $this->dump($prop) . ',' . PHP_EOL; $ident -= 4; } $str .= str_repeat(' ', $ident) . ')'; } elseif (is_array($value)) { $ident += 4; $str .= 'array('; $some = false; foreach ($value as $k => $v) { $str .= PHP_EOL . str_repeat(' ', $ident) . '"' . $k . '" => ' . $this->dump($v) . ','; $some = true; } $ident -= 4; $str .= ($some ? PHP_EOL . str_repeat(' ', $ident) : '') . ')'; } elseif (is_object($value)) { $str .= 'instanceof(' . get_debug_type($value) . ')'; } else { $str .= var_export($value, true); } return $str; } } orm/lib/Doctrine/ORM/Query/AST/NullComparisonExpression.php 0000644 00000001260 15120025735 0017604 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" * * @link www.doctrine-project.org */ class NullComparisonExpression extends Node { /** @var bool */ public $not; /** @var Node */ public $expression; /** @param Node $expression */ public function __construct($expression, bool $not = false) { $this->expression = $expression; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNullComparisonExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/NullIfExpression.php 0000644 00000001374 15120025735 0016036 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @link www.doctrine-project.org */ class NullIfExpression extends Node { /** @var mixed */ public $firstExpression; /** @var mixed */ public $secondExpression; /** * @param mixed $firstExpression * @param mixed $secondExpression */ public function __construct($firstExpression, $secondExpression) { $this->firstExpression = $firstExpression; $this->secondExpression = $secondExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkNullIfExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/OrderByClause.php 0000644 00000001104 15120025735 0015257 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * @link www.doctrine-project.org */ class OrderByClause extends Node { /** @var OrderByItem[] */ public $orderByItems = []; /** @param OrderByItem[] $orderByItems */ public function __construct(array $orderByItems) { $this->orderByItems = $orderByItems; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByClause($this); } } orm/lib/Doctrine/ORM/Query/AST/OrderByItem.php 0000644 00000001526 15120025735 0014751 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use function strtoupper; /** * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"] * * @link www.doctrine-project.org */ class OrderByItem extends Node { /** @var mixed */ public $expression; /** @var string */ public $type; /** @param mixed $expression */ public function __construct($expression) { $this->expression = $expression; } /** @return bool */ public function isAsc() { return strtoupper($this->type) === 'ASC'; } /** @return bool */ public function isDesc() { return strtoupper($this->type) === 'DESC'; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkOrderByItem($this); } } orm/lib/Doctrine/ORM/Query/AST/ParenthesisExpression.php 0000644 00000000727 15120025735 0017133 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ParenthesisExpression ::= "(" ArithmeticPrimary ")" */ class ParenthesisExpression extends Node { /** @var Node */ public $expression; public function __construct(Node $expression) { $this->expression = $expression; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkParenthesisExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/PartialObjectExpression.php 0000644 00000001042 15120025735 0017360 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class PartialObjectExpression extends Node { /** @var string */ public $identificationVariable; /** @var mixed[] */ public $partialFieldSet; /** * @param string $identificationVariable * @param mixed[] $partialFieldSet */ public function __construct($identificationVariable, array $partialFieldSet) { $this->identificationVariable = $identificationVariable; $this->partialFieldSet = $partialFieldSet; } } orm/lib/Doctrine/ORM/Query/AST/PathExpression.php 0000644 00000003465 15120025735 0015544 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * StateField ::= {EmbeddedClassStateField "."}* SimpleStateField * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField */ class PathExpression extends Node { public const TYPE_COLLECTION_VALUED_ASSOCIATION = 2; public const TYPE_SINGLE_VALUED_ASSOCIATION = 4; public const TYPE_STATE_FIELD = 8; /** * @var int|null * @psalm-var self::TYPE_*|null */ public $type; /** * @var int * @psalm-var int-mask-of<self::TYPE_*> */ public $expectedType; /** @var string */ public $identificationVariable; /** @var string|null */ public $field; /** * @param int $expectedType * @param string $identificationVariable * @param string|null $field * @psalm-param int-mask-of<self::TYPE_*> $expectedType */ public function __construct($expectedType, $identificationVariable, $field = null) { $this->expectedType = $expectedType; $this->identificationVariable = $identificationVariable; $this->field = $field; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkPathExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/QuantifiedExpression.php 0000644 00000001725 15120025735 0016736 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use function strtoupper; /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * @link www.doctrine-project.org */ class QuantifiedExpression extends Node { /** @var string */ public $type; /** @var Subselect */ public $subselect; /** @param Subselect $subselect */ public function __construct($subselect) { $this->subselect = $subselect; } /** @return bool */ public function isAll() { return strtoupper($this->type) === 'ALL'; } /** @return bool */ public function isAny() { return strtoupper($this->type) === 'ANY'; } /** @return bool */ public function isSome() { return strtoupper($this->type) === 'SOME'; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkQuantifiedExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/RangeVariableDeclaration.php 0000644 00000001735 15120025735 0017436 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org */ class RangeVariableDeclaration extends Node { /** @var string */ public $abstractSchemaName; /** @var string */ public $aliasIdentificationVariable; /** @var bool */ public $isRoot; /** * @param string $abstractSchemaName * @param string $aliasIdentificationVar * @param bool $isRoot */ public function __construct($abstractSchemaName, $aliasIdentificationVar, $isRoot = true) { $this->abstractSchemaName = $abstractSchemaName; $this->aliasIdentificationVariable = $aliasIdentificationVar; $this->isRoot = $isRoot; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkRangeVariableDeclaration($this); } } orm/lib/Doctrine/ORM/Query/AST/SelectClause.php 0000644 00000001367 15120025735 0015143 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SelectClause = "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * @link www.doctrine-project.org */ class SelectClause extends Node { /** @var bool */ public $isDistinct; /** @var mixed[] */ public $selectExpressions = []; /** * @param mixed[] $selectExpressions * @param bool $isDistinct */ public function __construct(array $selectExpressions, $isDistinct) { $this->isDistinct = $isDistinct; $this->selectExpressions = $selectExpressions; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectClause($this); } } orm/lib/Doctrine/ORM/Query/AST/SelectExpression.php 0000644 00000002231 15120025735 0016055 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SelectExpression ::= IdentificationVariable ["." "*"] | StateFieldPathExpression | * (AggregateExpression | "(" Subselect ")") [["AS"] ["HIDDEN"] FieldAliasIdentificationVariable] * * @link www.doctrine-project.org */ class SelectExpression extends Node { /** @var mixed */ public $expression; /** @var string|null */ public $fieldIdentificationVariable; /** @var bool */ public $hiddenAliasResultVariable; /** * @param mixed $expression * @param string|null $fieldIdentificationVariable * @param bool $hiddenAliasResultVariable */ public function __construct($expression, $fieldIdentificationVariable, $hiddenAliasResultVariable = false) { $this->expression = $expression; $this->fieldIdentificationVariable = $fieldIdentificationVariable; $this->hiddenAliasResultVariable = $hiddenAliasResultVariable; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/SelectStatement.php 0000644 00000001771 15120025735 0015672 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @link www.doctrine-project.org */ class SelectStatement extends Node { /** @var SelectClause */ public $selectClause; /** @var FromClause */ public $fromClause; /** @var WhereClause|null */ public $whereClause; /** @var GroupByClause|null */ public $groupByClause; /** @var HavingClause|null */ public $havingClause; /** @var OrderByClause|null */ public $orderByClause; /** * @param SelectClause $selectClause * @param FromClause $fromClause */ public function __construct($selectClause, $fromClause) { $this->selectClause = $selectClause; $this->fromClause = $fromClause; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSelectStatement($this); } } orm/lib/Doctrine/ORM/Query/AST/SimpleArithmeticExpression.php 0000644 00000001157 15120025735 0020107 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * @link www.doctrine-project.org */ class SimpleArithmeticExpression extends Node { /** @var mixed[] */ public $arithmeticTerms = []; /** @param mixed[] $arithmeticTerms */ public function __construct(array $arithmeticTerms) { $this->arithmeticTerms = $arithmeticTerms; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleArithmeticExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/SimpleCaseExpression.php 0000644 00000002031 15120025735 0016661 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * * @link www.doctrine-project.org */ class SimpleCaseExpression extends Node { /** @var PathExpression */ public $caseOperand = null; /** @var mixed[] */ public $simpleWhenClauses = []; /** @var mixed */ public $elseScalarExpression = null; /** * @param PathExpression $caseOperand * @param mixed[] $simpleWhenClauses * @param mixed $elseScalarExpression */ public function __construct($caseOperand, array $simpleWhenClauses, $elseScalarExpression) { $this->caseOperand = $caseOperand; $this->simpleWhenClauses = $simpleWhenClauses; $this->elseScalarExpression = $elseScalarExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleCaseExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/SimpleSelectClause.php 0000644 00000001503 15120025735 0016305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @link www.doctrine-project.org */ class SimpleSelectClause extends Node { /** @var bool */ public $isDistinct = false; /** @var SimpleSelectExpression */ public $simpleSelectExpression; /** * @param SimpleSelectExpression $simpleSelectExpression * @param bool $isDistinct */ public function __construct($simpleSelectExpression, $isDistinct) { $this->simpleSelectExpression = $simpleSelectExpression; $this->isDistinct = $isDistinct; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectClause($this); } } orm/lib/Doctrine/ORM/Query/AST/SimpleSelectExpression.php 0000644 00000001353 15120025735 0017233 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable * | (AggregateExpression [["AS"] FieldAliasIdentificationVariable]) * * @link www.doctrine-project.org */ class SimpleSelectExpression extends Node { /** @var Node|string */ public $expression; /** @var string */ public $fieldIdentificationVariable; /** @param Node|string $expression */ public function __construct($expression) { $this->expression = $expression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSimpleSelectExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/SimpleWhenClause.php 0000644 00000001463 15120025735 0015774 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * * @link www.doctrine-project.org */ class SimpleWhenClause extends Node { /** @var mixed */ public $caseScalarExpression = null; /** @var mixed */ public $thenScalarExpression = null; /** * @param mixed $caseScalarExpression * @param mixed $thenScalarExpression */ public function __construct($caseScalarExpression, $thenScalarExpression) { $this->caseScalarExpression = $caseScalarExpression; $this->thenScalarExpression = $thenScalarExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/Subselect.php 0000644 00000002137 15120025735 0014514 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @link www.doctrine-project.org */ class Subselect extends Node { /** @var SimpleSelectClause */ public $simpleSelectClause; /** @var SubselectFromClause */ public $subselectFromClause; /** @var WhereClause|null */ public $whereClause; /** @var GroupByClause|null */ public $groupByClause; /** @var HavingClause|null */ public $havingClause; /** @var OrderByClause|null */ public $orderByClause; /** * @param SimpleSelectClause $simpleSelectClause * @param SubselectFromClause $subselectFromClause */ public function __construct($simpleSelectClause, $subselectFromClause) { $this->simpleSelectClause = $simpleSelectClause; $this->subselectFromClause = $subselectFromClause; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSubselect($this); } } orm/lib/Doctrine/ORM/Query/AST/SubselectFromClause.php 0000644 00000001360 15120025735 0016472 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * @link www.doctrine-project.org */ class SubselectFromClause extends Node { /** @var mixed[] */ public $identificationVariableDeclarations = []; /** @param mixed[] $identificationVariableDeclarations */ public function __construct(array $identificationVariableDeclarations) { $this->identificationVariableDeclarations = $identificationVariableDeclarations; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkSubselectFromClause($this); } } orm/lib/Doctrine/ORM/Query/AST/SubselectIdentificationVariableDeclaration.php 0000644 00000001434 15120025735 0023201 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * SubselectIdentificationVariableDeclaration ::= AssociationPathExpression ["AS"] AliasIdentificationVariable * * @link www.doctrine-project.org */ class SubselectIdentificationVariableDeclaration { /** @var PathExpression */ public $associationPathExpression; /** @var string */ public $aliasIdentificationVariable; /** * @param PathExpression $associationPathExpression * @param string $aliasIdentificationVariable */ public function __construct($associationPathExpression, $aliasIdentificationVariable) { $this->associationPathExpression = $associationPathExpression; $this->aliasIdentificationVariable = $aliasIdentificationVariable; } } orm/lib/Doctrine/ORM/Query/AST/TypedExpression.php 0000644 00000000350 15120025735 0015723 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\DBAL\Types\Type; /** * Provides an API for resolving the type of a Node */ interface TypedExpression { public function getReturnType(): Type; } orm/lib/Doctrine/ORM/Query/AST/UpdateClause.php 0000644 00000001554 15120025735 0015144 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * UpdateClause ::= "UPDATE" AbstractSchemaName [["AS"] AliasIdentificationVariable] "SET" UpdateItem {"," UpdateItem}* * * @link www.doctrine-project.org */ class UpdateClause extends Node { /** @var string */ public $abstractSchemaName; /** @var string */ public $aliasIdentificationVariable; /** @var mixed[] */ public $updateItems = []; /** * @param string $abstractSchemaName * @param mixed[] $updateItems */ public function __construct($abstractSchemaName, array $updateItems) { $this->abstractSchemaName = $abstractSchemaName; $this->updateItems = $updateItems; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateClause($this); } } orm/lib/Doctrine/ORM/Query/AST/UpdateItem.php 0000644 00000001761 15120025735 0014626 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * UpdateItem ::= [IdentificationVariable "."] {StateField | SingleValuedAssociationField} "=" NewValue * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * @link www.doctrine-project.org */ class UpdateItem extends Node { /** @var PathExpression */ public $pathExpression; /** @var InputParameter|ArithmeticExpression|null */ public $newValue; /** * @param PathExpression $pathExpression * @param InputParameter|ArithmeticExpression|null $newValue */ public function __construct($pathExpression, $newValue) { $this->pathExpression = $pathExpression; $this->newValue = $newValue; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateItem($this); } } orm/lib/Doctrine/ORM/Query/AST/UpdateStatement.php 0000644 00000001145 15120025735 0015670 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * UpdateStatement = UpdateClause [WhereClause] * * @link www.doctrine-project.org */ class UpdateStatement extends Node { /** @var UpdateClause */ public $updateClause; /** @var WhereClause|null */ public $whereClause; /** @param UpdateClause $updateClause */ public function __construct($updateClause) { $this->updateClause = $updateClause; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkUpdateStatement($this); } } orm/lib/Doctrine/ORM/Query/AST/WhenClause.php 0000644 00000001547 15120025735 0014625 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * * @link www.doctrine-project.org */ class WhenClause extends Node { /** @var ConditionalExpression */ public $caseConditionExpression; /** @var mixed */ public $thenScalarExpression = null; /** * @param ConditionalExpression $caseConditionExpression * @param mixed $thenScalarExpression */ public function __construct($caseConditionExpression, $thenScalarExpression) { $this->caseConditionExpression = $caseConditionExpression; $this->thenScalarExpression = $thenScalarExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhenClauseExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/WhereClause.php 0000644 00000001162 15120025735 0014767 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * WhereClause ::= "WHERE" ConditionalExpression * * @link www.doctrine-project.org */ class WhereClause extends Node { /** @var ConditionalExpression|ConditionalTerm */ public $conditionalExpression; /** @param ConditionalExpression $conditionalExpression */ public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkWhereClause($this); } } orm/lib/Doctrine/ORM/Query/AST/ASTException.php 0000644 00000000735 15120025735 0015073 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\QueryException; use function get_debug_type; /** * Base exception class for AST exceptions. */ class ASTException extends QueryException { /** * @param Node $node * * @return ASTException */ public static function noDispatchForNode($node) { return new self('Double-dispatch for node ' . get_debug_type($node) . ' is not supported.'); } } orm/lib/Doctrine/ORM/Query/AST/AggregateExpression.php 0000644 00000001727 15120025735 0016535 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class AggregateExpression extends Node { /** @var string */ public $functionName; /** @var PathExpression|SimpleArithmeticExpression */ public $pathExpression; /** * Some aggregate expressions support distinct, eg COUNT. * * @var bool */ public $isDistinct = false; /** * @param string $functionName * @param PathExpression|SimpleArithmeticExpression $pathExpression * @param bool $isDistinct */ public function __construct($functionName, $pathExpression, $isDistinct) { $this->functionName = $functionName; $this->pathExpression = $pathExpression; $this->isDistinct = $isDistinct; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkAggregateExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/ArithmeticExpression.php 0000644 00000001413 15120025735 0016730 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * @link www.doctrine-project.org */ class ArithmeticExpression extends Node { /** @var SimpleArithmeticExpression|null */ public $simpleArithmeticExpression; /** @var Subselect|null */ public $subselect; /** @return bool */ public function isSimpleArithmeticExpression() { return (bool) $this->simpleArithmeticExpression; } /** @return bool */ public function isSubselect() { return (bool) $this->subselect; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkArithmeticExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/ArithmeticFactor.php 0000644 00000002033 15120025735 0016006 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * @link www.doctrine-project.org */ class ArithmeticFactor extends Node { /** @var mixed */ public $arithmeticPrimary; /** * NULL represents no sign, TRUE means positive and FALSE means negative sign. * * @var bool|null */ public $sign; /** * @param mixed $arithmeticPrimary * @param bool|null $sign */ public function __construct($arithmeticPrimary, $sign = null) { $this->arithmeticPrimary = $arithmeticPrimary; $this->sign = $sign; } /** @return bool */ public function isPositiveSigned() { return $this->sign === true; } /** @return bool */ public function isNegativeSigned() { return $this->sign === false; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticFactor($this); } } orm/lib/Doctrine/ORM/Query/AST/ArithmeticTerm.php 0000644 00000001124 15120025735 0015477 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * @link www.doctrine-project.org */ class ArithmeticTerm extends Node { /** @var mixed[] */ public $arithmeticFactors; /** @param mixed[] $arithmeticFactors */ public function __construct(array $arithmeticFactors) { $this->arithmeticFactors = $arithmeticFactors; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkArithmeticTerm($this); } } orm/lib/Doctrine/ORM/Query/AST/BetweenExpression.php 0000644 00000001656 15120025735 0016241 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class BetweenExpression extends Node { /** @var ArithmeticExpression */ public $expression; /** @var ArithmeticExpression */ public $leftBetweenExpression; /** @var ArithmeticExpression */ public $rightBetweenExpression; /** @var bool */ public $not; /** * @param ArithmeticExpression $expr * @param ArithmeticExpression $leftExpr * @param ArithmeticExpression $rightExpr */ public function __construct($expr, $leftExpr, $rightExpr, bool $not = false) { $this->expression = $expr; $this->leftBetweenExpression = $leftExpr; $this->rightBetweenExpression = $rightExpr; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkBetweenExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/CoalesceExpression.php 0000644 00000001160 15120025735 0016354 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * * @link www.doctrine-project.org */ class CoalesceExpression extends Node { /** @var mixed[] */ public $scalarExpressions = []; /** @param mixed[] $scalarExpressions */ public function __construct(array $scalarExpressions) { $this->scalarExpressions = $scalarExpressions; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkCoalesceExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/CollectionMemberExpression.php 0000644 00000001722 15120025735 0020065 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * @link www.doctrine-project.org */ class CollectionMemberExpression extends Node { /** @var mixed */ public $entityExpression; /** @var PathExpression */ public $collectionValuedPathExpression; /** @var bool */ public $not; /** * @param mixed $entityExpr * @param PathExpression $collValuedPathExpr */ public function __construct($entityExpr, $collValuedPathExpr, bool $not = false) { $this->entityExpression = $entityExpr; $this->collectionValuedPathExpression = $collValuedPathExpr; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkCollectionMemberExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/ComparisonExpression.php 0000644 00000002666 15120025735 0016764 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) | * StringExpression ComparisonOperator (StringExpression | QuantifiedExpression) | * BooleanExpression ("=" | "<>" | "!=") (BooleanExpression | QuantifiedExpression) | * EnumExpression ("=" | "<>" | "!=") (EnumExpression | QuantifiedExpression) | * DatetimeExpression ComparisonOperator (DatetimeExpression | QuantifiedExpression) | * EntityExpression ("=" | "<>") (EntityExpression | QuantifiedExpression) * * @link www.doctrine-project.org */ class ComparisonExpression extends Node { /** @var Node|string */ public $leftExpression; /** @var Node|string */ public $rightExpression; /** @var string */ public $operator; /** * @param Node|string $leftExpr * @param string $operator * @param Node|string $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpression = $leftExpr; $this->rightExpression = $rightExpr; $this->operator = $operator; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkComparisonExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/ConditionalExpression.php 0000644 00000001140 15120025735 0017077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * @link www.doctrine-project.org */ class ConditionalExpression extends Node { /** @var mixed[] */ public $conditionalTerms = []; /** @param mixed[] $conditionalTerms */ public function __construct(array $conditionalTerms) { $this->conditionalTerms = $conditionalTerms; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/ConditionalFactor.php 0000644 00000001310 15120025735 0016155 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * @link www.doctrine-project.org */ class ConditionalFactor extends Node { /** @var bool */ public $not = false; /** @var ConditionalPrimary */ public $conditionalPrimary; /** @param ConditionalPrimary $conditionalPrimary */ public function __construct($conditionalPrimary, bool $not = false) { $this->conditionalPrimary = $conditionalPrimary; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalFactor($this); } } orm/lib/Doctrine/ORM/Query/AST/ConditionalPrimary.php 0000644 00000001465 15120025735 0016375 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * @link www.doctrine-project.org */ class ConditionalPrimary extends Node { /** @var Node|null */ public $simpleConditionalExpression; /** @var ConditionalExpression|null */ public $conditionalExpression; /** @return bool */ public function isSimpleConditionalExpression() { return (bool) $this->simpleConditionalExpression; } /** @return bool */ public function isConditionalExpression() { return (bool) $this->conditionalExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalPrimary($this); } } orm/lib/Doctrine/ORM/Query/AST/ConditionalTerm.php 0000644 00000001135 15120025735 0015653 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * @link www.doctrine-project.org */ class ConditionalTerm extends Node { /** @var mixed[] */ public $conditionalFactors = []; /** @param mixed[] $conditionalFactors */ public function __construct(array $conditionalFactors) { $this->conditionalFactors = $conditionalFactors; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkConditionalTerm($this); } } orm/lib/Doctrine/ORM/Query/AST/DeleteClause.php 0000644 00000001245 15120025735 0015121 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName [["AS"] AliasIdentificationVariable] * * @link www.doctrine-project.org */ class DeleteClause extends Node { /** @var string */ public $abstractSchemaName; /** @var string */ public $aliasIdentificationVariable; /** @param string $abstractSchemaName */ public function __construct($abstractSchemaName) { $this->abstractSchemaName = $abstractSchemaName; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteClause($this); } } orm/lib/Doctrine/ORM/Query/AST/DeleteStatement.php 0000644 00000001145 15120025735 0015650 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * DeleteStatement = DeleteClause [WhereClause] * * @link www.doctrine-project.org */ class DeleteStatement extends Node { /** @var DeleteClause */ public $deleteClause; /** @var WhereClause|null */ public $whereClause; /** @param DeleteClause $deleteClause */ public function __construct($deleteClause) { $this->deleteClause = $deleteClause; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkDeleteStatement($this); } } orm/lib/Doctrine/ORM/Query/AST/EmptyCollectionComparisonExpression.php 0000644 00000001327 15120025735 0022010 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * @link www.doctrine-project.org */ class EmptyCollectionComparisonExpression extends Node { /** @var PathExpression */ public $expression; /** @var bool */ public $not; /** @param PathExpression $expression */ public function __construct($expression, bool $not = false) { $this->expression = $expression; $this->not = $not; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkEmptyCollectionComparisonExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/GeneralCaseExpression.php 0000644 00000001462 15120025735 0017014 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * * @link www.doctrine-project.org */ class GeneralCaseExpression extends Node { /** @var mixed[] */ public $whenClauses = []; /** @var mixed */ public $elseScalarExpression = null; /** * @param mixed[] $whenClauses * @param mixed $elseScalarExpression */ public function __construct(array $whenClauses, $elseScalarExpression) { $this->whenClauses = $whenClauses; $this->elseScalarExpression = $elseScalarExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkGeneralCaseExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/HavingClause.php 0000644 00000001003 15120025735 0015123 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class HavingClause extends Node { /** @var ConditionalExpression */ public $conditionalExpression; /** @param ConditionalExpression $conditionalExpression */ public function __construct($conditionalExpression) { $this->conditionalExpression = $conditionalExpression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkHavingClause($this); } } orm/lib/Doctrine/ORM/Query/AST/InExpression.php 0000644 00000002517 15120025735 0015213 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\Deprecations\Deprecation; /** * InExpression ::= ArithmeticExpression ["NOT"] "IN" "(" (Literal {"," Literal}* | Subselect) ")" * * @deprecated Use {@see InListExpression} or {@see InSubselectExpression} instead. */ class InExpression extends Node { /** @var bool */ public $not; /** @var ArithmeticExpression */ public $expression; /** @var mixed[] */ public $literals = []; /** @var Subselect|null */ public $subselect; /** @param ArithmeticExpression $expression */ public function __construct($expression) { if (! $this instanceof InListExpression && ! $this instanceof InSubselectExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10267', '%s is deprecated, use %s or %s instead.', self::class, InListExpression::class, InSubselectExpression::class ); } $this->expression = $expression; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { // We still call the deprecated method in order to not break existing custom SQL walkers. return $sqlWalker->walkInExpression($this); } } orm/lib/Doctrine/ORM/Query/AST/InSubselectExpression.php 0000644 00000000625 15120025735 0017063 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; class InSubselectExpression extends InExpression { /** @var Subselect */ public $subselect; public function __construct(ArithmeticExpression $expression, Subselect $subselect, bool $not = false) { $this->subselect = $subselect; $this->not = $not; parent::__construct($expression); } } orm/lib/Doctrine/ORM/Query/AST/InputParameter.php 0000644 00000001450 15120025735 0015520 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\QueryException; use function is_numeric; use function strlen; use function substr; class InputParameter extends Node { /** @var bool */ public $isNamed; /** @var string */ public $name; /** * @param string $value * * @throws QueryException */ public function __construct($value) { if (strlen($value) === 1) { throw QueryException::invalidParameterFormat($value); } $param = substr($value, 1); $this->isNamed = ! is_numeric($param); $this->name = $param; } /** * {@inheritDoc} */ public function dispatch($walker) { return $walker->walkInputParameter($this); } } orm/lib/Doctrine/ORM/Query/AST/Join.php 0000644 00000002256 15120025735 0013464 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\AST; /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression * ["AS"] AliasIdentificationVariable [("ON" | "WITH") ConditionalExpression] * * @link www.doctrine-project.org */ class Join extends Node { public const JOIN_TYPE_LEFT = 1; public const JOIN_TYPE_LEFTOUTER = 2; public const JOIN_TYPE_INNER = 3; /** * @var int * @psalm-var self::JOIN_TYPE_* */ public $joinType = self::JOIN_TYPE_INNER; /** @var Node|null */ public $joinAssociationDeclaration = null; /** @var ConditionalExpression|null */ public $conditionalExpression = null; /** * @param int $joinType * @param Node $joinAssociationDeclaration * @psalm-param self::JOIN_TYPE_* $joinType */ public function __construct($joinType, $joinAssociationDeclaration) { $this->joinType = $joinType; $this->joinAssociationDeclaration = $joinAssociationDeclaration; } /** * {@inheritDoc} */ public function dispatch($sqlWalker) { return $sqlWalker->walkJoin($this); } } orm/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php 0000644 00000003026 15120025735 0016760 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; /** * Base class for SQL statement executors. * * @link http://www.doctrine-project.org * * @todo Rename: AbstractSQLExecutor */ abstract class AbstractSqlExecutor { /** @var list<string>|string */ protected $_sqlStatements; /** @var QueryCacheProfile */ protected $queryCacheProfile; /** * Gets the SQL statements that are executed by the executor. * * @return mixed[]|string All the SQL update statements. */ public function getSqlStatements() { return $this->_sqlStatements; } /** @return void */ public function setQueryCacheProfile(QueryCacheProfile $qcp) { $this->queryCacheProfile = $qcp; } /** * Do not use query cache * * @return void */ public function removeQueryCacheProfile() { $this->queryCacheProfile = null; } /** * Executes all sql statements. * * @param Connection $conn The database connection that is used to execute the queries. * @psalm-param list<mixed>|array<string, mixed> $params The parameters. * @psalm-param array<int, int|string|Type|null>| * array<string, int|string|Type|null> $types The parameter types. * * @return Result|int */ abstract public function execute(Connection $conn, array $params, array $types); } orm/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php 0000644 00000012144 15120025735 0017723 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\AST\DeleteStatement; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Utility\PersisterHelper; use Throwable; use function array_merge; use function array_reverse; use function implode; /** * Executes the SQL statements for bulk DQL DELETE statements on classes in * Class Table Inheritance (JOINED). * * @link http://www.doctrine-project.org */ class MultiTableDeleteExecutor extends AbstractSqlExecutor { /** @var string */ private $createTempTableSql; /** @var string */ private $dropTempTableSql; /** @var string */ private $insertSql; /** * Initializes a new <tt>MultiTableDeleteExecutor</tt>. * * Internal note: Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. * * @param DeleteStatement $AST The root AST node of the DQL query. * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); if ($conn instanceof PrimaryReadReplicaConnection) { $conn->ensureConnectedToPrimary(); } $primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName); $primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable; $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias); $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias); $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]); $this->insertSql .= $sqlWalker->walkFromClause($fromClause); // Append WHERE clause, if there is one. if ($AST->whereClause) { $this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store DELETE statements $classNames = array_merge($primaryClass->parentClasses, [$primaryClass->name], $primaryClass->subClasses); foreach (array_reverse($classNames) as $className) { $tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform); $this->_sqlStatements[] = 'DELETE FROM ' . $tableName . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } // 4. Store DDL for temporary identifier table. $columnDefinitions = []; foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = [ 'notnull' => true, 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)), ]; } $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))'; $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} * * @return int */ public function execute(Connection $conn, array $params, array $types) { // Create temporary id table $conn->executeStatement($this->createTempTableSql); try { // Insert identifiers $numDeleted = $conn->executeStatement($this->insertSql, $params, $types); // Execute DELETE statements foreach ($this->_sqlStatements as $sql) { $conn->executeStatement($sql); } } catch (Throwable $exception) { // FAILURE! Drop temporary table to avoid possible collisions $conn->executeStatement($this->dropTempTableSql); // Re-throw exception throw $exception; } // Drop temporary table $conn->executeStatement($this->dropTempTableSql); return $numDeleted; } } orm/lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php 0000644 00000015575 15120025735 0017756 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\AST\UpdateStatement; use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Utility\PersisterHelper; use function array_merge; use function array_reverse; use function array_slice; use function implode; /** * Executes the SQL statements for bulk DQL UPDATE statements on classes in * Class Table Inheritance (JOINED). */ class MultiTableUpdateExecutor extends AbstractSqlExecutor { /** @var string */ private $createTempTableSql; /** @var string */ private $dropTempTableSql; /** @var string */ private $insertSql; /** @var mixed[] */ private $sqlParameters = []; /** @var int */ private $numParametersInUpdateClause = 0; /** * Initializes a new <tt>MultiTableUpdateExecutor</tt>. * * Internal note: Any SQL construction and preparation takes place in the constructor for * best performance. With a query cache the executor will be cached. * * @param UpdateStatement $AST The root AST node of the DQL query. * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST. */ public function __construct(AST\Node $AST, $sqlWalker) { $em = $sqlWalker->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); if ($conn instanceof PrimaryReadReplicaConnection) { $conn->ensureConnectedToPrimary(); } $updateClause = $AST->updateClause; $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName); $rootClass = $em->getClassMetadata($primaryClass->rootEntityName); $updateItems = $updateClause->updateItems; $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); $idColumnNames = $rootClass->getIdentifierColumnNames(); $idColumnList = implode(', ', $idColumnNames); // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable); $this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . implode(', t0.', $idColumnNames); $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable); $fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]); $this->insertSql .= $sqlWalker->walkFromClause($fromClause); // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect) $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable; // 3. Create and store UPDATE statements $classNames = array_merge($primaryClass->parentClasses, [$primaryClass->name], $primaryClass->subClasses); $i = -1; foreach (array_reverse($classNames) as $className) { $affected = false; $class = $em->getClassMetadata($className); $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET '; foreach ($updateItems as $updateItem) { $field = $updateItem->pathExpression->field; if ( (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited'])) || (isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) ) { $newValue = $updateItem->newValue; if (! $affected) { $affected = true; ++$i; } else { $updateSql .= ', '; } $updateSql .= $sqlWalker->walkUpdateItem($updateItem); if ($newValue instanceof AST\InputParameter) { $this->sqlParameters[$i][] = $newValue->name; ++$this->numParametersInUpdateClause; } } } if ($affected) { $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')'; } } // Append WHERE clause to insertSql, if there is one. if ($AST->whereClause) { $this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); } // 4. Store DDL for temporary identifier table. $columnDefinitions = []; foreach ($idColumnNames as $idColumnName) { $columnDefinitions[$idColumnName] = [ 'notnull' => true, 'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)), ]; } $this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))'; $this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); } /** * {@inheritDoc} * * @return int */ public function execute(Connection $conn, array $params, array $types) { // Create temporary id table $conn->executeStatement($this->createTempTableSql); try { // Insert identifiers. Parameters from the update clause are cut off. $numUpdated = $conn->executeStatement( $this->insertSql, array_slice($params, $this->numParametersInUpdateClause), array_slice($types, $this->numParametersInUpdateClause) ); // Execute UPDATE statements foreach ($this->_sqlStatements as $key => $statement) { $paramValues = []; $paramTypes = []; if (isset($this->sqlParameters[$key])) { foreach ($this->sqlParameters[$key] as $parameterKey => $parameterName) { $paramValues[] = $params[$parameterKey]; $paramTypes[] = $types[$parameterKey] ?? ParameterTypeInferer::inferType($params[$parameterKey]); } } $conn->executeStatement($statement, $paramValues, $paramTypes); } } finally { // Drop temporary table $conn->executeStatement($this->dropTempTableSql); } return $numUpdated; } } orm/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php 0000644 00000001450 15120025735 0017115 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Result; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\SqlWalker; /** * Executor that executes the SQL statement for simple DQL SELECT statements. * * @link www.doctrine-project.org */ class SingleSelectExecutor extends AbstractSqlExecutor { public function __construct(SelectStatement $AST, SqlWalker $sqlWalker) { $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); } /** * {@inheritDoc} * * @return Result */ public function execute(Connection $conn, array $params, array $types) { return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile); } } orm/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php 0000644 00000002417 15120025735 0021217 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Exec; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\SqlWalker; /** * Executor that executes the SQL statements for DQL DELETE/UPDATE statements on classes * that are mapped to a single table. * * @link www.doctrine-project.org * * @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor. */ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor { /** @param SqlWalker $sqlWalker */ public function __construct(AST\Node $AST, $sqlWalker) { if ($AST instanceof AST\UpdateStatement) { $this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST); } elseif ($AST instanceof AST\DeleteStatement) { $this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST); } } /** * {@inheritDoc} * * @return int */ public function execute(Connection $conn, array $params, array $types) { if ($conn instanceof PrimaryReadReplicaConnection) { $conn->ensureConnectedToPrimary(); } return $conn->executeStatement($this->_sqlStatements, $params, $types); } } orm/lib/Doctrine/ORM/Query/Expr/Andx.php 0000644 00000001175 15120025735 0013745 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL and parts. * * @link www.doctrine-project.org */ class Andx extends Composite { /** @var string */ protected $separator = ' AND '; /** @var string[] */ protected $allowedClasses = [ Comparison::class, Func::class, Orx::class, self::class, ]; /** @psalm-var list<string|Comparison|Func|Orx|self> */ protected $parts = []; /** @psalm-return list<string|Comparison|Func|Orx|self> */ public function getParts() { return $this->parts; } } orm/lib/Doctrine/ORM/Query/Expr/Base.php 0000644 00000004332 15120025735 0013723 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; use InvalidArgumentException; use Stringable; use function count; use function get_class; use function get_debug_type; use function implode; use function in_array; use function is_string; use function sprintf; /** * Abstract base Expr class for building DQL parts. * * @link www.doctrine-project.org */ abstract class Base { /** @var string */ protected $preSeparator = '('; /** @var string */ protected $separator = ', '; /** @var string */ protected $postSeparator = ')'; /** @var list<class-string> */ protected $allowedClasses = []; /** @var list<string|Stringable> */ protected $parts = []; /** @param mixed $args */ public function __construct($args = []) { $this->addMultiple($args); } /** * @param string[]|object[]|string|object $args * @psalm-param list<string|object>|string|object $args * * @return $this */ public function addMultiple($args = []) { foreach ((array) $args as $arg) { $this->add($arg); } return $this; } /** * @param mixed $arg * * @return $this * * @throws InvalidArgumentException */ public function add($arg) { if ($arg !== null && (! $arg instanceof self || $arg->count() > 0)) { // If we decide to keep Expr\Base instances, we can use this check if (! is_string($arg) && ! in_array(get_class($arg), $this->allowedClasses, true)) { throw new InvalidArgumentException(sprintf( "Expression of type '%s' not allowed in this context.", get_debug_type($arg) )); } $this->parts[] = $arg; } return $this; } /** * @return int * @psalm-return 0|positive-int */ public function count() { return count($this->parts); } /** @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } orm/lib/Doctrine/ORM/Query/Expr/Comparison.php 0000644 00000002507 15120025735 0015165 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL comparison expressions. * * @link www.doctrine-project.org */ class Comparison { public const EQ = '='; public const NEQ = '<>'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; /** @var mixed */ protected $leftExpr; /** @var string */ protected $operator; /** @var mixed */ protected $rightExpr; /** * Creates a comparison expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** @return string */ public function getOperator() { return $this->operator; } /** @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** @return string */ public function __toString() { return $this->leftExpr . ' ' . $this->operator . ' ' . $this->rightExpr; } } orm/lib/Doctrine/ORM/Query/Expr/Composite.php 0000644 00000002301 15120025735 0015005 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; use function implode; use function is_object; use function preg_match; /** * Expression class for building DQL and parts. * * @link www.doctrine-project.org */ class Composite extends Base { /** @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } $components = []; foreach ($this->parts as $part) { $components[] = $this->processQueryPart($part); } return implode($this->separator, $components); } /** @param string|object $part */ private function processQueryPart($part): string { $queryPart = (string) $part; if (is_object($part) && $part instanceof self && $part->count() > 1) { return $this->preSeparator . $queryPart . $this->postSeparator; } // Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND") if (preg_match('/\s(OR|AND)\s/i', $queryPart)) { return $this->preSeparator . $queryPart . $this->postSeparator; } return $queryPart; } } orm/lib/Doctrine/ORM/Query/Expr/From.php 0000644 00000002201 15120025735 0013745 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL from. * * @link www.doctrine-project.org */ class From { /** @var string */ protected $from; /** @var string */ protected $alias; /** @var string|null */ protected $indexBy; /** * @param string $from The class name. * @param string $alias The alias of the class. * @param string $indexBy The index for the from. */ public function __construct($from, $alias, $indexBy = null) { $this->from = $from; $this->alias = $alias; $this->indexBy = $indexBy; } /** @return string */ public function getFrom() { return $this->from; } /** @return string */ public function getAlias() { return $this->alias; } /** @return string|null */ public function getIndexBy() { return $this->indexBy; } /** @return string */ public function __toString() { return $this->from . ' ' . $this->alias . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : ''); } } orm/lib/Doctrine/ORM/Query/Expr/Func.php 0000644 00000001753 15120025735 0013750 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; use function implode; /** * Expression class for generating DQL functions. * * @link www.doctrine-project.org */ class Func { /** @var string */ protected $name; /** @var mixed[] */ protected $arguments; /** * Creates a function, with the given argument. * * @param string $name * @param mixed[]|mixed $arguments * @psalm-param list<mixed>|mixed $arguments */ public function __construct($name, $arguments) { $this->name = $name; $this->arguments = (array) $arguments; } /** @return string */ public function getName() { return $this->name; } /** @psalm-return list<mixed> */ public function getArguments() { return $this->arguments; } /** @return string */ public function __toString() { return $this->name . '(' . implode(', ', $this->arguments) . ')'; } } orm/lib/Doctrine/ORM/Query/Expr/GroupBy.php 0000644 00000000753 15120025735 0014443 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL Group By parts. * * @link www.doctrine-project.org */ class GroupBy extends Base { /** @var string */ protected $preSeparator = ''; /** @var string */ protected $postSeparator = ''; /** @psalm-var list<string> */ protected $parts = []; /** @psalm-return list<string> */ public function getParts() { return $this->parts; } } orm/lib/Doctrine/ORM/Query/Expr/Join.php 0000644 00000006025 15120025735 0013751 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; use function strtoupper; /** * Expression class for DQL join. * * @link www.doctrine-project.org */ class Join { public const INNER_JOIN = 'INNER'; public const LEFT_JOIN = 'LEFT'; public const ON = 'ON'; public const WITH = 'WITH'; /** * @var string * @psalm-var self::INNER_JOIN|self::LEFT_JOIN */ protected $joinType; /** @var string */ protected $join; /** @var string|null */ protected $alias; /** * @var string|null * @psalm-var self::ON|self::WITH|null */ protected $conditionType; /** @var string|Comparison|Composite|Func|null */ protected $condition; /** @var string|null */ protected $indexBy; /** * @param string $joinType The condition type constant. Either INNER_JOIN or LEFT_JOIN. * @param string $join The relationship to join. * @param string|null $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|Comparison|Composite|Func|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * @psalm-param self::INNER_JOIN|self::LEFT_JOIN $joinType * @psalm-param self::ON|self::WITH|null $conditionType */ public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null) { $this->joinType = $joinType; $this->join = $join; $this->alias = $alias; $this->conditionType = $conditionType; $this->condition = $condition; $this->indexBy = $indexBy; } /** * @return string * @psalm-return self::INNER_JOIN|self::LEFT_JOIN */ public function getJoinType() { return $this->joinType; } /** @return string */ public function getJoin() { return $this->join; } /** @return string|null */ public function getAlias() { return $this->alias; } /** * @return string|null * @psalm-return self::ON|self::WITH|null */ public function getConditionType() { return $this->conditionType; } /** @return string|Comparison|Composite|Func|null */ public function getCondition() { return $this->condition; } /** @return string|null */ public function getIndexBy() { return $this->indexBy; } /** @return string */ public function __toString() { return strtoupper($this->joinType) . ' JOIN ' . $this->join . ($this->alias ? ' ' . $this->alias : '') . ($this->indexBy ? ' INDEX BY ' . $this->indexBy : '') . ($this->condition ? ' ' . strtoupper($this->conditionType) . ' ' . $this->condition : ''); } } orm/lib/Doctrine/ORM/Query/Expr/Literal.php 0000644 00000000750 15120025735 0014445 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for generating DQL functions. * * @link www.doctrine-project.org */ class Literal extends Base { /** @var string */ protected $preSeparator = ''; /** @var string */ protected $postSeparator = ''; /** @psalm-var list<string> */ protected $parts = []; /** @psalm-return list<string> */ public function getParts() { return $this->parts; } } orm/lib/Doctrine/ORM/Query/Expr/Math.php 0000644 00000003004 15120025735 0013735 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for DQL math statements. * * @link www.doctrine-project.org */ class Math { /** @var mixed */ protected $leftExpr; /** @var string */ protected $operator; /** @var mixed */ protected $rightExpr; /** * Creates a mathematical expression with the given arguments. * * @param mixed $leftExpr * @param string $operator * @param mixed $rightExpr */ public function __construct($leftExpr, $operator, $rightExpr) { $this->leftExpr = $leftExpr; $this->operator = $operator; $this->rightExpr = $rightExpr; } /** @return mixed */ public function getLeftExpr() { return $this->leftExpr; } /** @return string */ public function getOperator() { return $this->operator; } /** @return mixed */ public function getRightExpr() { return $this->rightExpr; } /** @return string */ public function __toString() { // Adjusting Left Expression $leftExpr = (string) $this->leftExpr; if ($this->leftExpr instanceof Math) { $leftExpr = '(' . $leftExpr . ')'; } // Adjusting Right Expression $rightExpr = (string) $this->rightExpr; if ($this->rightExpr instanceof Math) { $rightExpr = '(' . $rightExpr . ')'; } return $leftExpr . ' ' . $this->operator . ' ' . $rightExpr; } } orm/lib/Doctrine/ORM/Query/Expr/OrderBy.php 0000644 00000002654 15120025736 0014425 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; use function count; use function implode; /** * Expression class for building DQL Order By parts. * * @link www.doctrine-project.org */ class OrderBy { /** @var string */ protected $preSeparator = ''; /** @var string */ protected $separator = ', '; /** @var string */ protected $postSeparator = ''; /** @var string[] */ protected $allowedClasses = []; /** @psalm-var list<string> */ protected $parts = []; /** * @param string|null $sort * @param string|null $order */ public function __construct($sort = null, $order = null) { if ($sort) { $this->add($sort, $order); } } /** * @param string $sort * @param string|null $order * * @return void */ public function add($sort, $order = null) { $order = ! $order ? 'ASC' : $order; $this->parts[] = $sort . ' ' . $order; } /** * @return int * @psalm-return 0|positive-int */ public function count() { return count($this->parts); } /** @psalm-return list<string> */ public function getParts() { return $this->parts; } /** @return string */ public function __toString() { return $this->preSeparator . implode($this->separator, $this->parts) . $this->postSeparator; } } orm/lib/Doctrine/ORM/Query/Expr/Orx.php 0000644 00000001177 15120025736 0013626 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL OR clauses. * * @link www.doctrine-project.org */ class Orx extends Composite { /** @var string */ protected $separator = ' OR '; /** @var string[] */ protected $allowedClasses = [ Comparison::class, Func::class, Andx::class, self::class, ]; /** @psalm-var list<string|Comparison|Func|Andx|self> */ protected $parts = []; /** @psalm-return list<string|Comparison|Func|Andx|self> */ public function getParts() { return $this->parts; } } orm/lib/Doctrine/ORM/Query/Expr/Select.php 0000644 00000001100 15120025736 0014257 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Expr; /** * Expression class for building DQL select statements. * * @link www.doctrine-project.org */ class Select extends Base { /** @var string */ protected $preSeparator = ''; /** @var string */ protected $postSeparator = ''; /** @var string[] */ protected $allowedClasses = [Func::class]; /** @psalm-var list<string|Func> */ protected $parts = []; /** @psalm-return list<string|Func> */ public function getParts() { return $this->parts; } } orm/lib/Doctrine/ORM/Query/Filter/FilterException.php 0000644 00000001146 15120025736 0016465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Filter; use Doctrine\ORM\Exception\ORMException; use function sprintf; class FilterException extends ORMException { public static function cannotConvertListParameterIntoSingleValue(string $name): self { return new self(sprintf('Cannot convert list-based SQL filter parameter "%s" into a single value.', $name)); } public static function cannotConvertSingleParameterIntoListValue(string $name): self { return new self(sprintf('Cannot convert single SQL filter parameter "%s" into a list value.', $name)); } } orm/lib/Doctrine/ORM/Query/Filter/SQLFilter.php 0000644 00000013365 15120025736 0015174 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query\Filter; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ParameterTypeInferer; use InvalidArgumentException; use function array_map; use function implode; use function ksort; use function serialize; /** * The base class that user defined filters should extend. * * Handles the setting and escaping of parameters. * * @abstract */ abstract class SQLFilter { /** * The entity manager. * * @var EntityManagerInterface */ private $em; /** * Parameters for the filter. * * @psalm-var array<string,array{type: string, value: mixed, is_list: bool}> */ private $parameters = []; /** @param EntityManagerInterface $em The entity manager. */ final public function __construct(EntityManagerInterface $em) { $this->em = $em; } /** * Sets a parameter list that can be used by the filter. * * @param string $name Name of the parameter. * @param array<mixed> $values List of parameter values. * @param string $type The parameter type. If specified, the given value will be run through * the type conversion of this type. * * @return $this */ final public function setParameterList(string $name, array $values, string $type = Types::STRING): self { $this->parameters[$name] = ['value' => $values, 'type' => $type, 'is_list' => true]; // Keep the parameters sorted for the hash ksort($this->parameters); // The filter collection of the EM is now dirty $this->em->getFilters()->setFiltersStateDirty(); return $this; } /** * Sets a parameter that can be used by the filter. * * @param string $name Name of the parameter. * @param mixed $value Value of the parameter. * @param string|null $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return $this */ final public function setParameter($name, $value, $type = null): self { if ($type === null) { $type = ParameterTypeInferer::inferType($value); } $this->parameters[$name] = ['value' => $value, 'type' => $type, 'is_list' => false]; // Keep the parameters sorted for the hash ksort($this->parameters); // The filter collection of the EM is now dirty $this->em->getFilters()->setFiltersStateDirty(); return $this; } /** * Gets a parameter to use in a query. * * The function is responsible for the right output escaping to use the * value in a query. * * @param string $name Name of the parameter. * * @return string The SQL escaped parameter to use in a query. * * @throws InvalidArgumentException */ final public function getParameter($name) { if (! isset($this->parameters[$name])) { throw new InvalidArgumentException("Parameter '" . $name . "' does not exist."); } if ($this->parameters[$name]['is_list']) { throw FilterException::cannotConvertListParameterIntoSingleValue($name); } $param = $this->parameters[$name]; return $this->em->getConnection()->quote($param['value'], $param['type']); } /** * Gets a parameter to use in a query assuming it's a list of entries. * * The function is responsible for the right output escaping to use the * value in a query, separating each entry by comma to inline it into * an IN() query part. * * @param string $name Name of the parameter. * * @return string The SQL escaped parameter to use in a query. * * @throws InvalidArgumentException */ final public function getParameterList(string $name): string { if (! isset($this->parameters[$name])) { throw new InvalidArgumentException("Parameter '" . $name . "' does not exist."); } if ($this->parameters[$name]['is_list'] === false) { throw FilterException::cannotConvertSingleParameterIntoListValue($name); } $param = $this->parameters[$name]; $connection = $this->em->getConnection(); $quoted = array_map(static function ($value) use ($connection, $param) { return $connection->quote($value, $param['type']); }, $param['value']); return implode(',', $quoted); } /** * Checks if a parameter was set for the filter. * * @param string $name Name of the parameter. * * @return bool */ final public function hasParameter($name) { return isset($this->parameters[$name]); } /** * Returns as string representation of the SQLFilter parameters (the state). * * @return string String representation of the SQLFilter. */ final public function __toString() { return serialize($this->parameters); } /** * Returns the database connection used by the entity manager */ final protected function getConnection(): Connection { return $this->em->getConnection(); } /** * Gets the SQL query part to add to a query. * * @param string $targetTableAlias * @psalm-param ClassMetadata<object> $targetEntity * * @return string The constraint SQL if there is available, empty string otherwise. */ abstract public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias); } orm/lib/Doctrine/ORM/Query/Expr.php 0000644 00000045413 15120025736 0013057 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Traversable; use function func_get_args; use function implode; use function is_bool; use function is_float; use function is_int; use function is_iterable; use function iterator_to_array; use function str_replace; /** * This class is used to generate DQL expressions via a set of PHP static functions. * * @link www.doctrine-project.org * * @todo Rename: ExpressionBuilder */ class Expr { /** * Creates a conjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) AND (u.role = ?2) * $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2')); * * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string $x Optional clause. Defaults to null, * but requires at least one defined * when converting to string. * @psalm-param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x * * @return Expr\Andx */ public function andX($x = null) { return new Expr\Andx(func_get_args()); } /** * Creates a disjunction of the given boolean expressions. * * Example: * * [php] * // (u.type = ?1) OR (u.role = ?2) * $q->where($q->expr()->orX('u.type = ?1', 'u.role = ?2')); * * @param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string $x Optional clause. Defaults to null, * but requires at least one defined * when converting to string. * @psalm-param Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x * * @return Expr\Orx */ public function orX($x = null) { return new Expr\Orx(func_get_args()); } /** * Creates an ASCending order expression. * * @param mixed $expr * * @return Expr\OrderBy */ public function asc($expr) { return new Expr\OrderBy($expr, 'ASC'); } /** * Creates a DESCending order expression. * * @param mixed $expr * * @return Expr\OrderBy */ public function desc($expr) { return new Expr\OrderBy($expr, 'DESC'); } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> = <right expr>. Example: * * [php] * // u.id = ?1 * $expr->eq('u.id', '?1'); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function eq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::EQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <> <right expr>. Example: * * [php] * // u.id <> ?1 * $q->where($q->expr()->neq('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function neq($x, $y) { return new Expr\Comparison($x, Expr\Comparison::NEQ, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> < <right expr>. Example: * * [php] * // u.id < ?1 * $q->where($q->expr()->lt('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function lt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <= <right expr>. Example: * * [php] * // u.id <= ?1 * $q->where($q->expr()->lte('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function lte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::LTE, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> > <right expr>. Example: * * [php] * // u.id > ?1 * $q->where($q->expr()->gt('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function gt($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GT, $y); } /** * Creates an instance of Expr\Comparison, with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> >= <right expr>. Example: * * [php] * // u.id >= ?1 * $q->where($q->expr()->gte('u.id', '?1')); * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Comparison */ public function gte($x, $y) { return new Expr\Comparison($x, Expr\Comparison::GTE, $y); } /** * Creates an instance of AVG() function, with the given argument. * * @param mixed $x Argument to be used in AVG() function. * * @return Expr\Func */ public function avg($x) { return new Expr\Func('AVG', [$x]); } /** * Creates an instance of MAX() function, with the given argument. * * @param mixed $x Argument to be used in MAX() function. * * @return Expr\Func */ public function max($x) { return new Expr\Func('MAX', [$x]); } /** * Creates an instance of MIN() function, with the given argument. * * @param mixed $x Argument to be used in MIN() function. * * @return Expr\Func */ public function min($x) { return new Expr\Func('MIN', [$x]); } /** * Creates an instance of COUNT() function, with the given argument. * * @param mixed $x Argument to be used in COUNT() function. * * @return Expr\Func */ public function count($x) { return new Expr\Func('COUNT', [$x]); } /** * Creates an instance of COUNT(DISTINCT) function, with the given argument. * * @param mixed $x Argument to be used in COUNT(DISTINCT) function. * * @return string */ public function countDistinct($x) { return 'COUNT(DISTINCT ' . implode(', ', func_get_args()) . ')'; } /** * Creates an instance of EXISTS() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in EXISTS() function. * * @return Expr\Func */ public function exists($subquery) { return new Expr\Func('EXISTS', [$subquery]); } /** * Creates an instance of ALL() function, with the given DQL Subquery. * * @param mixed $subquery DQL Subquery to be used in ALL() function. * * @return Expr\Func */ public function all($subquery) { return new Expr\Func('ALL', [$subquery]); } /** * Creates a SOME() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in SOME() function. * * @return Expr\Func */ public function some($subquery) { return new Expr\Func('SOME', [$subquery]); } /** * Creates an ANY() function expression with the given DQL subquery. * * @param mixed $subquery DQL Subquery to be used in ANY() function. * * @return Expr\Func */ public function any($subquery) { return new Expr\Func('ANY', [$subquery]); } /** * Creates a negation expression of the given restriction. * * @param mixed $restriction Restriction to be used in NOT() function. * * @return Expr\Func */ public function not($restriction) { return new Expr\Func('NOT', [$restriction]); } /** * Creates an ABS() function expression with the given argument. * * @param mixed $x Argument to be used in ABS() function. * * @return Expr\Func */ public function abs($x) { return new Expr\Func('ABS', [$x]); } /** * Creates a MOD($x, $y) function expression to return the remainder of $x divided by $y. * * @param mixed $x * @param mixed $y */ public function mod($x, $y): Expr\Func { return new Expr\Func('MOD', [$x, $y]); } /** * Creates a product mathematical expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> * <right expr>. Example: * * [php] * // u.salary * u.percentAnnualSalaryIncrease * $q->expr()->prod('u.salary', 'u.percentAnnualSalaryIncrease') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function prod($x, $y) { return new Expr\Math($x, '*', $y); } /** * Creates a difference mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> - <right expr>. Example: * * [php] * // u.monthlySubscriptionCount - 1 * $q->expr()->diff('u.monthlySubscriptionCount', '1') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function diff($x, $y) { return new Expr\Math($x, '-', $y); } /** * Creates a sum mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> + <right expr>. Example: * * [php] * // u.numChildren + 1 * $q->expr()->sum('u.numChildren', '1') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function sum($x, $y) { return new Expr\Math($x, '+', $y); } /** * Creates a quotient mathematical expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> / <right expr>. Example: * * [php] * // u.total / u.period * $expr->quot('u.total', 'u.period') * * @param mixed $x Left expression. * @param mixed $y Right expression. * * @return Expr\Math */ public function quot($x, $y) { return new Expr\Math($x, '/', $y); } /** * Creates a SQRT() function expression with the given argument. * * @param mixed $x Argument to be used in SQRT() function. * * @return Expr\Func */ public function sqrt($x) { return new Expr\Func('SQRT', [$x]); } /** * Creates an IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by IN() function. * @param mixed $y Argument to be used in IN() function. * * @return Expr\Func */ public function in($x, $y) { if (is_iterable($y)) { if ($y instanceof Traversable) { $y = iterator_to_array($y); } foreach ($y as &$literal) { if (! ($literal instanceof Expr\Literal)) { $literal = $this->quoteLiteral($literal); } } } return new Expr\Func($x . ' IN', (array) $y); } /** * Creates a NOT IN() expression with the given arguments. * * @param string $x Field in string format to be restricted by NOT IN() function. * @param mixed $y Argument to be used in NOT IN() function. * * @return Expr\Func */ public function notIn($x, $y) { if (is_iterable($y)) { if ($y instanceof Traversable) { $y = iterator_to_array($y); } foreach ($y as &$literal) { if (! ($literal instanceof Expr\Literal)) { $literal = $this->quoteLiteral($literal); } } } return new Expr\Func($x . ' NOT IN', (array) $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NULL. * * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x Field in string format to be restricted by IS NOT NULL. * * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * * @return Expr\Comparison */ public function like($x, $y) { return new Expr\Comparison($x, 'LIKE', $y); } /** * Creates a NOT LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * * @return Expr\Comparison */ public function notLike($x, $y) { return new Expr\Comparison($x, 'NOT LIKE', $y); } /** * Creates a CONCAT() function expression with the given arguments. * * @param mixed $x First argument to be used in CONCAT() function. * @param mixed $y,... Other arguments to be used in CONCAT() function. * * @return Expr\Func */ public function concat($x, $y) { return new Expr\Func('CONCAT', func_get_args()); } /** * Creates a SUBSTRING() function expression with the given arguments. * * @param mixed $x Argument to be used as string to be cropped by SUBSTRING() function. * @param int $from Initial offset to start cropping string. May accept negative values. * @param int|null $len Length of crop. May accept negative values. * * @return Expr\Func */ public function substring($x, $from, $len = null) { $args = [$x, $from]; if ($len !== null) { $args[] = $len; } return new Expr\Func('SUBSTRING', $args); } /** * Creates a LOWER() function expression with the given argument. * * @param mixed $x Argument to be used in LOWER() function. * * @return Expr\Func A LOWER function expression. */ public function lower($x) { return new Expr\Func('LOWER', [$x]); } /** * Creates an UPPER() function expression with the given argument. * * @param mixed $x Argument to be used in UPPER() function. * * @return Expr\Func An UPPER function expression. */ public function upper($x) { return new Expr\Func('UPPER', [$x]); } /** * Creates a LENGTH() function expression with the given argument. * * @param mixed $x Argument to be used as argument of LENGTH() function. * * @return Expr\Func A LENGTH function expression. */ public function length($x) { return new Expr\Func('LENGTH', [$x]); } /** * Creates a literal expression of the given argument. * * @param scalar $literal Argument to be converted to literal. * * @return Expr\Literal */ public function literal($literal) { return new Expr\Literal($this->quoteLiteral($literal)); } /** * Quotes a literal value, if necessary, according to the DQL syntax. * * @param scalar $literal The literal value. */ private function quoteLiteral($literal): string { if (is_int($literal) || is_float($literal)) { return (string) $literal; } if (is_bool($literal)) { return $literal ? 'true' : 'false'; } return "'" . str_replace("'", "''", $literal) . "'"; } /** * Creates an instance of BETWEEN() function, with the given argument. * * @param mixed $val Valued to be inspected by range values. * @param int|string $x Starting range value to be used in BETWEEN() function. * @param int|string $y End point value to be used in BETWEEN() function. * * @return string A BETWEEN expression. */ public function between($val, $x, $y) { return $val . ' BETWEEN ' . $x . ' AND ' . $y; } /** * Creates an instance of TRIM() function, with the given argument. * * @param mixed $x Argument to be used as argument of TRIM() function. * * @return Expr\Func a TRIM expression. */ public function trim($x) { return new Expr\Func('TRIM', $x); } /** * Creates an instance of MEMBER OF function, with the given arguments. * * @param string $x Value to be checked * @param string $y Value to be checked against * * @return Expr\Comparison */ public function isMemberOf($x, $y) { return new Expr\Comparison($x, 'MEMBER OF', $y); } /** * Creates an instance of INSTANCE OF function, with the given arguments. * * @param string $x Value to be checked * @param string $y Value to be checked against * * @return Expr\Comparison */ public function isInstanceOf($x, $y) { return new Expr\Comparison($x, 'INSTANCE OF', $y); } } orm/lib/Doctrine/ORM/Query/FilterCollection.php 0000644 00000012054 15120025736 0015375 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\Filter\SQLFilter; use InvalidArgumentException; use function assert; use function ksort; /** * Collection class for all the query filters. */ class FilterCollection { /* Filter STATES */ /** * A filter object is in CLEAN state when it has no changed parameters. */ public const FILTERS_STATE_CLEAN = 1; /** * A filter object is in DIRTY state when it has changed parameters. */ public const FILTERS_STATE_DIRTY = 2; /** * The used Configuration. * * @var Configuration */ private $config; /** * The EntityManager that "owns" this FilterCollection instance. * * @var EntityManagerInterface */ private $em; /** * Instances of enabled filters. * * @var SQLFilter[] * @psalm-var array<string, SQLFilter> */ private $enabledFilters = []; /** * The filter hash from the last time the query was parsed. * * @var string */ private $filterHash = ''; /** * The current state of this filter. * * @var int * @psalm-var self::FILTERS_STATE_* */ private $filtersState = self::FILTERS_STATE_CLEAN; public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->config = $em->getConfiguration(); } /** * Gets all the enabled filters. * * @return SQLFilter[] The enabled filters. * @psalm-return array<string, SQLFilter> */ public function getEnabledFilters() { return $this->enabledFilters; } /** * Enables a filter from the collection. * * @param string $name Name of the filter. * * @return SQLFilter The enabled filter. * * @throws InvalidArgumentException If the filter does not exist. */ public function enable($name) { if (! $this->has($name)) { throw new InvalidArgumentException("Filter '" . $name . "' does not exist."); } if (! $this->isEnabled($name)) { $filterClass = $this->config->getFilterClassName($name); assert($filterClass !== null); $this->enabledFilters[$name] = new $filterClass($this->em); // Keep the enabled filters sorted for the hash ksort($this->enabledFilters); $this->setFiltersStateDirty(); } return $this->enabledFilters[$name]; } /** * Disables a filter. * * @param string $name Name of the filter. * * @return SQLFilter The disabled filter. * * @throws InvalidArgumentException If the filter does not exist. */ public function disable($name) { // Get the filter to return it $filter = $this->getFilter($name); unset($this->enabledFilters[$name]); $this->setFiltersStateDirty(); return $filter; } /** * Gets an enabled filter from the collection. * * @param string $name Name of the filter. * * @return SQLFilter The filter. * * @throws InvalidArgumentException If the filter is not enabled. */ public function getFilter($name) { if (! $this->isEnabled($name)) { throw new InvalidArgumentException("Filter '" . $name . "' is not enabled."); } return $this->enabledFilters[$name]; } /** * Checks whether filter with given name is defined. * * @param string $name Name of the filter. * * @return bool true if the filter exists, false if not. */ public function has($name) { return $this->config->getFilterClassName($name) !== null; } /** * Checks if a filter is enabled. * * @param string $name Name of the filter. * * @return bool True if the filter is enabled, false otherwise. */ public function isEnabled($name) { return isset($this->enabledFilters[$name]); } /** * Checks if the filter collection is clean. * * @return bool */ public function isClean() { return $this->filtersState === self::FILTERS_STATE_CLEAN; } /** * Generates a string of currently enabled filters to use for the cache id. * * @return string */ public function getHash() { // If there are only clean filters, the previous hash can be returned if ($this->filtersState === self::FILTERS_STATE_CLEAN) { return $this->filterHash; } $filterHash = ''; foreach ($this->enabledFilters as $name => $filter) { $filterHash .= $name . $filter; } $this->filterHash = $filterHash; $this->filtersState = self::FILTERS_STATE_CLEAN; return $filterHash; } /** * Sets the filter state to dirty. * * @return void */ public function setFiltersStateDirty() { $this->filtersState = self::FILTERS_STATE_DIRTY; } } orm/lib/Doctrine/ORM/Query/Lexer.php 0000644 00000016570 15120025736 0013222 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\Common\Lexer\AbstractLexer; use Doctrine\Deprecations\Deprecation; use function constant; use function ctype_alpha; use function defined; use function is_numeric; use function str_contains; use function str_replace; use function stripos; use function strlen; use function strtoupper; use function substr; /** * Scans a DQL query for tokens. * * @extends AbstractLexer<Lexer::T_*, string> */ class Lexer extends AbstractLexer { // All tokens that are not valid identifiers must be < 100 public const T_NONE = 1; public const T_INTEGER = 2; public const T_STRING = 3; public const T_INPUT_PARAMETER = 4; public const T_FLOAT = 5; public const T_CLOSE_PARENTHESIS = 6; public const T_OPEN_PARENTHESIS = 7; public const T_COMMA = 8; public const T_DIVIDE = 9; public const T_DOT = 10; public const T_EQUALS = 11; public const T_GREATER_THAN = 12; public const T_LOWER_THAN = 13; public const T_MINUS = 14; public const T_MULTIPLY = 15; public const T_NEGATE = 16; public const T_PLUS = 17; public const T_OPEN_CURLY_BRACE = 18; public const T_CLOSE_CURLY_BRACE = 19; // All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100 /** @deprecated No Replacement planned. */ public const T_ALIASED_NAME = 100; public const T_FULLY_QUALIFIED_NAME = 101; public const T_IDENTIFIER = 102; // All keyword tokens should be >= 200 public const T_ALL = 200; public const T_AND = 201; public const T_ANY = 202; public const T_AS = 203; public const T_ASC = 204; public const T_AVG = 205; public const T_BETWEEN = 206; public const T_BOTH = 207; public const T_BY = 208; public const T_CASE = 209; public const T_COALESCE = 210; public const T_COUNT = 211; public const T_DELETE = 212; public const T_DESC = 213; public const T_DISTINCT = 214; public const T_ELSE = 215; public const T_EMPTY = 216; public const T_END = 217; public const T_ESCAPE = 218; public const T_EXISTS = 219; public const T_FALSE = 220; public const T_FROM = 221; public const T_GROUP = 222; public const T_HAVING = 223; public const T_HIDDEN = 224; public const T_IN = 225; public const T_INDEX = 226; public const T_INNER = 227; public const T_INSTANCE = 228; public const T_IS = 229; public const T_JOIN = 230; public const T_LEADING = 231; public const T_LEFT = 232; public const T_LIKE = 233; public const T_MAX = 234; public const T_MEMBER = 235; public const T_MIN = 236; public const T_NEW = 237; public const T_NOT = 238; public const T_NULL = 239; public const T_NULLIF = 240; public const T_OF = 241; public const T_OR = 242; public const T_ORDER = 243; public const T_OUTER = 244; public const T_PARTIAL = 245; public const T_SELECT = 246; public const T_SET = 247; public const T_SOME = 248; public const T_SUM = 249; public const T_THEN = 250; public const T_TRAILING = 251; public const T_TRUE = 252; public const T_UPDATE = 253; public const T_WHEN = 254; public const T_WHERE = 255; public const T_WITH = 256; /** * Creates a new query scanner object. * * @param string $input A query string. */ public function __construct($input) { $this->setInput($input); } /** * {@inheritDoc} */ protected function getCatchablePatterns() { return [ '[a-z_][a-z0-9_]*\:[a-z_][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // aliased name '[a-z_\\\][a-z0-9_]*(?:\\\[a-z_][a-z0-9_]*)*', // identifier or qualified name '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers "'(?:[^']|'')*'", // quoted strings '\?[0-9]*|:[a-z_][a-z0-9_]*', // parameters ]; } /** * {@inheritDoc} */ protected function getNonCatchablePatterns() { return ['\s+', '--.*', '(.)']; } /** * {@inheritDoc} */ protected function getType(&$value) { $type = self::T_NONE; switch (true) { // Recognize numeric values case is_numeric($value): if (str_contains($value, '.') || stripos($value, 'e') !== false) { return self::T_FLOAT; } return self::T_INTEGER; // Recognize quoted strings case $value[0] === "'": $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); return self::T_STRING; // Recognize identifiers, aliased or qualified names case ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\': $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value); if (defined($name)) { $type = constant($name); if ($type > 100) { return $type; } } if (str_contains($value, ':')) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.', $value ); return self::T_ALIASED_NAME; } if (str_contains($value, '\\')) { return self::T_FULLY_QUALIFIED_NAME; } return self::T_IDENTIFIER; // Recognize input parameters case $value[0] === '?' || $value[0] === ':': return self::T_INPUT_PARAMETER; // Recognize symbols case $value === '.': return self::T_DOT; case $value === ',': return self::T_COMMA; case $value === '(': return self::T_OPEN_PARENTHESIS; case $value === ')': return self::T_CLOSE_PARENTHESIS; case $value === '=': return self::T_EQUALS; case $value === '>': return self::T_GREATER_THAN; case $value === '<': return self::T_LOWER_THAN; case $value === '+': return self::T_PLUS; case $value === '-': return self::T_MINUS; case $value === '*': return self::T_MULTIPLY; case $value === '/': return self::T_DIVIDE; case $value === '!': return self::T_NEGATE; case $value === '{': return self::T_OPEN_CURLY_BRACE; case $value === '}': return self::T_CLOSE_CURLY_BRACE; // Default default: // Do nothing } return $type; } } orm/lib/Doctrine/ORM/Query/Parameter.php 0000644 00000004173 15120025736 0014057 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use function trim; /** * Defines a Query Parameter. * * @link www.doctrine-project.org */ class Parameter { /** * Returns the internal representation of a parameter name. * * @param string|int $name The parameter name or position. * * @return string The normalized parameter name. */ public static function normalizeName($name) { return trim((string) $name, ':'); } /** * The parameter name. * * @var string */ private $name; /** * The parameter value. * * @var mixed */ private $value; /** * The parameter type. * * @var mixed */ private $type; /** * Whether the parameter type was explicitly specified or not * * @var bool */ private $typeSpecified; /** * @param string|int $name Parameter name * @param mixed $value Parameter value * @param mixed $type Parameter type */ public function __construct($name, $value, $type = null) { $this->name = self::normalizeName($name); $this->typeSpecified = $type !== null; $this->setValue($value, $type); } /** * Retrieves the Parameter name. * * @return string */ public function getName() { return $this->name; } /** * Retrieves the Parameter value. * * @return mixed */ public function getValue() { return $this->value; } /** * Retrieves the Parameter type. * * @return mixed */ public function getType() { return $this->type; } /** * Defines the Parameter value. * * @param mixed $value Parameter value. * @param mixed $type Parameter type. * * @return void */ public function setValue($value, $type = null) { $this->value = $value; $this->type = $type ?: ParameterTypeInferer::inferType($value); } public function typeWasSpecified(): bool { return $this->typeSpecified; } } orm/lib/Doctrine/ORM/Query/ParameterTypeInferer.php 0000644 00000003432 15120025736 0016231 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use BackedEnum; use DateInterval; use DateTimeImmutable; use DateTimeInterface; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Types\Types; use function current; use function is_array; use function is_bool; use function is_int; /** * Provides an enclosed support for parameter inferring. * * @link www.doctrine-project.org */ class ParameterTypeInferer { /** * Infers type of a given value, returning a compatible constant: * - Type (\Doctrine\DBAL\Types\Type::*) * - Connection (\Doctrine\DBAL\Connection::PARAM_*) * * @param mixed $value Parameter value. * * @return int|string Parameter type constant. */ public static function inferType($value) { if (is_int($value)) { return Types::INTEGER; } if (is_bool($value)) { return Types::BOOLEAN; } if ($value instanceof DateTimeImmutable) { return Types::DATETIME_IMMUTABLE; } if ($value instanceof DateTimeInterface) { return Types::DATETIME_MUTABLE; } if ($value instanceof DateInterval) { return Types::DATEINTERVAL; } if ($value instanceof BackedEnum) { return is_int($value->value) ? Types::INTEGER : Types::STRING; } if (is_array($value)) { $firstValue = current($value); if ($firstValue instanceof BackedEnum) { $firstValue = $firstValue->value; } return is_int($firstValue) ? Connection::PARAM_INT_ARRAY : Connection::PARAM_STR_ARRAY; } return ParameterType::STRING; } } orm/lib/Doctrine/ORM/Query/Parser.php 0000644 00000353606 15120025736 0013403 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\Common\Lexer\Token; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\Functions; use LogicException; use ReflectionClass; use function array_intersect; use function array_search; use function assert; use function class_exists; use function count; use function explode; use function implode; use function in_array; use function interface_exists; use function is_string; use function sprintf; use function str_contains; use function strlen; use function strpos; use function strrpos; use function strtolower; use function substr; /** * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language. * Parses a DQL query, reports any errors in it, and generates an AST. * * @psalm-import-type AssociationMapping from ClassMetadata * @psalm-type DqlToken = Token<Lexer::T_*, string> * @psalm-type QueryComponent = array{ * metadata?: ClassMetadata<object>, * parent?: string|null, * relation?: AssociationMapping|null, * map?: string|null, * resultVariable?: AST\Node|string, * nestingLevel: int, * token: DqlToken, * } */ class Parser { /** * @readonly Maps BUILT-IN string function names to AST class names. * @psalm-var array<string, class-string<Functions\FunctionNode>> */ private static $stringFunctions = [ 'concat' => Functions\ConcatFunction::class, 'substring' => Functions\SubstringFunction::class, 'trim' => Functions\TrimFunction::class, 'lower' => Functions\LowerFunction::class, 'upper' => Functions\UpperFunction::class, 'identity' => Functions\IdentityFunction::class, ]; /** * @readonly Maps BUILT-IN numeric function names to AST class names. * @psalm-var array<string, class-string<Functions\FunctionNode>> */ private static $numericFunctions = [ 'length' => Functions\LengthFunction::class, 'locate' => Functions\LocateFunction::class, 'abs' => Functions\AbsFunction::class, 'sqrt' => Functions\SqrtFunction::class, 'mod' => Functions\ModFunction::class, 'size' => Functions\SizeFunction::class, 'date_diff' => Functions\DateDiffFunction::class, 'bit_and' => Functions\BitAndFunction::class, 'bit_or' => Functions\BitOrFunction::class, // Aggregate functions 'min' => Functions\MinFunction::class, 'max' => Functions\MaxFunction::class, 'avg' => Functions\AvgFunction::class, 'sum' => Functions\SumFunction::class, 'count' => Functions\CountFunction::class, ]; /** * @readonly Maps BUILT-IN datetime function names to AST class names. * @psalm-var array<string, class-string<Functions\FunctionNode>> */ private static $datetimeFunctions = [ 'current_date' => Functions\CurrentDateFunction::class, 'current_time' => Functions\CurrentTimeFunction::class, 'current_timestamp' => Functions\CurrentTimestampFunction::class, 'date_add' => Functions\DateAddFunction::class, 'date_sub' => Functions\DateSubFunction::class, ]; /* * Expressions that were encountered during parsing of identifiers and expressions * and still need to be validated. */ /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */ private $deferredIdentificationVariables = []; /** @psalm-var list<array{token: DqlToken|null, expression: AST\PartialObjectExpression, nestingLevel: int}> */ private $deferredPartialObjectExpressions = []; /** @psalm-var list<array{token: DqlToken|null, expression: AST\PathExpression, nestingLevel: int}> */ private $deferredPathExpressions = []; /** @psalm-var list<array{token: DqlToken|null, expression: mixed, nestingLevel: int}> */ private $deferredResultVariables = []; /** @psalm-var list<array{token: DqlToken|null, expression: AST\NewObjectExpression, nestingLevel: int}> */ private $deferredNewObjectExpressions = []; /** * The lexer. * * @var Lexer */ private $lexer; /** * The parser result. * * @var ParserResult */ private $parserResult; /** * The EntityManager. * * @var EntityManagerInterface */ private $em; /** * The Query to parse. * * @var Query */ private $query; /** * Map of declared query components in the parsed query. * * @psalm-var array<string, QueryComponent> */ private $queryComponents = []; /** * Keeps the nesting level of defined ResultVariables. * * @var int */ private $nestingLevel = 0; /** * Any additional custom tree walkers that modify the AST. * * @psalm-var list<class-string<TreeWalker>> */ private $customTreeWalkers = []; /** * The custom last tree walker, if any, that is responsible for producing the output. * * @var class-string<SqlWalker>|null */ private $customOutputWalker; /** @psalm-var array<string, AST\SelectExpression> */ private $identVariableExpressions = []; /** * Creates a new query parser object. * * @param Query $query The Query to parse. */ public function __construct(Query $query) { $this->query = $query; $this->em = $query->getEntityManager(); $this->lexer = new Lexer((string) $query->getDQL()); $this->parserResult = new ParserResult(); } /** * Sets a custom tree walker that produces output. * This tree walker will be run last over the AST, after any other walkers. * * @param string $className * @psalm-param class-string<SqlWalker> $className * * @return void */ public function setCustomOutputTreeWalker($className) { $this->customOutputWalker = $className; } /** * Adds a custom tree walker for modifying the AST. * * @param string $className * @psalm-param class-string<TreeWalker> $className * * @return void */ public function addCustomTreeWalker($className) { $this->customTreeWalkers[] = $className; } /** * Gets the lexer used by the parser. * * @return Lexer */ public function getLexer() { return $this->lexer; } /** * Gets the ParserResult that is being filled with information during parsing. * * @return ParserResult */ public function getParserResult() { return $this->parserResult; } /** * Gets the EntityManager used by the parser. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->em; } /** * Parses and builds AST for the given Query. * * @return AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement */ public function getAST() { // Parse & build AST $AST = $this->QueryLanguage(); // Process any deferred validations of some nodes in the AST. // This also allows post-processing of the AST for modification purposes. $this->processDeferredIdentificationVariables(); if ($this->deferredPartialObjectExpressions) { $this->processDeferredPartialObjectExpressions(); } if ($this->deferredPathExpressions) { $this->processDeferredPathExpressions(); } if ($this->deferredResultVariables) { $this->processDeferredResultVariables(); } if ($this->deferredNewObjectExpressions) { $this->processDeferredNewObjectExpressions($AST); } $this->processRootEntityAliasSelected(); // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! $this->fixIdentificationVariableOrder($AST); return $AST; } /** * Attempts to match the given token with the current lookahead token. * * If they match, updates the lookahead token; otherwise raises a syntax * error. * * @param Lexer::T_* $token The token type. * * @return void * * @throws QueryException If the tokens don't match. */ public function match($token) { $lookaheadType = $this->lexer->lookahead->type ?? null; // Short-circuit on first condition, usually types match if ($lookaheadType === $token) { $this->lexer->moveNext(); return; } // If parameter is not identifier (1-99) must be exact match if ($token < Lexer::T_IDENTIFIER) { $this->syntaxError($this->lexer->getLiteral($token)); } // If parameter is keyword (200+) must be exact match if ($token > Lexer::T_IDENTIFIER) { $this->syntaxError($this->lexer->getLiteral($token)); } // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+) if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) { $this->syntaxError($this->lexer->getLiteral($token)); } $this->lexer->moveNext(); } /** * Frees this parser, enabling it to be reused. * * @param bool $deep Whether to clean peek and reset errors. * @param int $position Position to reset. * * @return void */ public function free($deep = false, $position = 0) { // WARNING! Use this method with care. It resets the scanner! $this->lexer->resetPosition($position); // Deep = true cleans peek and also any previously defined errors if ($deep) { $this->lexer->resetPeek(); } $this->lexer->token = null; $this->lexer->lookahead = null; } /** * Parses a query string. * * @return ParserResult */ public function parse() { $AST = $this->getAST(); $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); if ($customWalkers !== false) { $this->customTreeWalkers = $customWalkers; } $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER); if ($customOutputWalker !== false) { $this->customOutputWalker = $customOutputWalker; } // Run any custom tree walkers over the AST if ($this->customTreeWalkers) { $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents); foreach ($this->customTreeWalkers as $walker) { $treeWalkerChain->addTreeWalker($walker); } switch (true) { case $AST instanceof AST\UpdateStatement: $treeWalkerChain->walkUpdateStatement($AST); break; case $AST instanceof AST\DeleteStatement: $treeWalkerChain->walkDeleteStatement($AST); break; case $AST instanceof AST\SelectStatement: default: $treeWalkerChain->walkSelectStatement($AST); } $this->queryComponents = $treeWalkerChain->getQueryComponents(); } $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class; $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents); // Assign an SQL executor to the parser result $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); return $this->parserResult; } /** * Fixes order of identification variables. * * They have to appear in the select clause in the same order as the * declarations (from ... x join ... y join ... z ...) appear in the query * as the hydration process relies on that order for proper operation. * * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST */ private function fixIdentificationVariableOrder(AST\Node $AST): void { if (count($this->identVariableExpressions) <= 1) { return; } assert($AST instanceof AST\SelectStatement); foreach ($this->queryComponents as $dqlAlias => $qComp) { if (! isset($this->identVariableExpressions[$dqlAlias])) { continue; } $expr = $this->identVariableExpressions[$dqlAlias]; $key = array_search($expr, $AST->selectClause->selectExpressions, true); unset($AST->selectClause->selectExpressions[$key]); $AST->selectClause->selectExpressions[] = $expr; } } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param mixed[]|null $token Got token. * @psalm-param DqlToken|null $token * * @return void * @psalm-return no-return * * @throws QueryException */ public function syntaxError($expected = '', $token = null) { if ($token === null) { $token = $this->lexer->lookahead; } $tokenPos = $token->position ?? '-1'; $message = sprintf('line 0, col %d: Error: ', $tokenPos); $message .= $expected !== '' ? sprintf('Expected %s, got ', $expected) : 'Unexpected '; $message .= $this->lexer->lookahead === null ? 'end of string.' : sprintf("'%s'", $token->value); throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL() ?? '')); } /** * Generates a new semantical error. * * @param string $message Optional message. * @param mixed[]|null $token Optional token. * @psalm-param DqlToken|null $token * * @return void * @psalm-return no-return * * @throws QueryException */ public function semanticalError($message = '', $token = null) { if ($token === null) { $token = $this->lexer->lookahead ?? new Token('fake token', 42, 0); } // Minimum exposed chars ahead of token $distance = 12; // Find a position of a final word to display in error string $dql = $this->query->getDQL(); $length = strlen($dql); $pos = $token->position + $distance; $pos = strpos($dql, ' ', $length > $pos ? $pos : $length); $length = $pos !== false ? $pos - $token->position : $distance; $tokenPos = $token->position > 0 ? $token->position : '-1'; $tokenStr = substr($dql, $token->position, $length); // Building informative message $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL())); } /** * Peeks beyond the matched closing parenthesis and returns the first token after that one. * * @param bool $resetPeek Reset peek after finding the closing parenthesis. * * @return mixed[] * @psalm-return DqlToken|null */ private function peekBeyondClosingParenthesis(bool $resetPeek = true) { $token = $this->lexer->peek(); $numUnmatched = 1; while ($numUnmatched > 0 && $token !== null) { switch ($token->type) { case Lexer::T_OPEN_PARENTHESIS: ++$numUnmatched; break; case Lexer::T_CLOSE_PARENTHESIS: --$numUnmatched; break; default: // Do nothing } $token = $this->lexer->peek(); } if ($resetPeek) { $this->lexer->resetPeek(); } return $token; } /** * Checks if the given token indicates a mathematical operator. * * @psalm-param DqlToken|null $token */ private function isMathOperator($token): bool { return $token !== null && in_array($token->type, [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY], true); } /** * Checks if the next-next (after lookahead) token starts a function. * * @return bool TRUE if the next-next tokens start a function, FALSE otherwise. */ private function isFunction(): bool { assert($this->lexer->lookahead !== null); $lookaheadType = $this->lexer->lookahead->type; $peek = $this->lexer->peek(); $this->lexer->resetPeek(); return $lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek->type === Lexer::T_OPEN_PARENTHESIS; } /** * Checks whether the given token type indicates an aggregate function. * * @psalm-param Lexer::T_* $tokenType * * @return bool TRUE if the token type is an aggregate function, FALSE otherwise. */ private function isAggregateFunction(int $tokenType): bool { return in_array( $tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT], true ); } /** * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. */ private function isNextAllAnySome(): bool { assert($this->lexer->lookahead !== null); return in_array( $this->lexer->lookahead->type, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true ); } /** * Validates that the given <tt>IdentificationVariable</tt> is semantically correct. * It must exist in query components list. */ private function processDeferredIdentificationVariables(): void { foreach ($this->deferredIdentificationVariables as $deferredItem) { $identVariable = $deferredItem['expression']; // Check if IdentificationVariable exists in queryComponents if (! isset($this->queryComponents[$identVariable])) { $this->semanticalError( sprintf("'%s' is not defined.", $identVariable), $deferredItem['token'] ); } $qComp = $this->queryComponents[$identVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if (! isset($qComp['metadata'])) { $this->semanticalError( sprintf("'%s' does not point to a Class.", $identVariable), $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( sprintf("'%s' is used outside the scope of its declaration.", $identVariable), $deferredItem['token'] ); } } } /** * Validates that the given <tt>NewObjectExpression</tt>. */ private function processDeferredNewObjectExpressions(AST\SelectStatement $AST): void { foreach ($this->deferredNewObjectExpressions as $deferredItem) { $expression = $deferredItem['expression']; $token = $deferredItem['token']; $className = $expression->className; $args = $expression->args; $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null; // If the namespace is not given then assumes the first FROM entity namespace if (! str_contains($className, '\\') && ! class_exists($className) && is_string($fromClassName) && str_contains($fromClassName, '\\')) { $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\')); $fqcn = $namespace . '\\' . $className; if (class_exists($fqcn)) { $expression->className = $fqcn; $className = $fqcn; } } if (! class_exists($className)) { $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token); } $class = new ReflectionClass($className); if (! $class->isInstantiable()) { $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token); } if ($class->getConstructor() === null) { $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token); } if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) { $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token); } } } /** * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct. * It must exist in query components list. */ private function processDeferredPartialObjectExpressions(): void { foreach ($this->deferredPartialObjectExpressions as $deferredItem) { $expr = $deferredItem['expression']; $class = $this->getMetadataForDqlAlias($expr->identificationVariable); foreach ($expr->partialFieldSet as $field) { if (isset($class->fieldMappings[$field])) { continue; } if ( isset($class->associationMappings[$field]) && $class->associationMappings[$field]['isOwningSide'] && $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE ) { continue; } $this->semanticalError(sprintf( "There is no mapped field named '%s' on class %s.", $field, $class->name ), $deferredItem['token']); } if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) { $this->semanticalError( 'The partial field selection of class ' . $class->name . ' must contain the identifier.', $deferredItem['token'] ); } } } /** * Validates that the given <tt>ResultVariable</tt> is semantically correct. * It must exist in query components list. */ private function processDeferredResultVariables(): void { foreach ($this->deferredResultVariables as $deferredItem) { $resultVariable = $deferredItem['expression']; // Check if ResultVariable exists in queryComponents if (! isset($this->queryComponents[$resultVariable])) { $this->semanticalError( sprintf("'%s' is not defined.", $resultVariable), $deferredItem['token'] ); } $qComp = $this->queryComponents[$resultVariable]; // Check if queryComponent points to an AbstractSchemaName or a ResultVariable if (! isset($qComp['resultVariable'])) { $this->semanticalError( sprintf("'%s' does not point to a ResultVariable.", $resultVariable), $deferredItem['token'] ); } // Validate if identification variable nesting level is lower or equal than the current one if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { $this->semanticalError( sprintf("'%s' is used outside the scope of its declaration.", $resultVariable), $deferredItem['token'] ); } } } /** * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules: * * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * StateFieldPathExpression ::= IdentificationVariable "." StateField * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField */ private function processDeferredPathExpressions(): void { foreach ($this->deferredPathExpressions as $deferredItem) { $pathExpression = $deferredItem['expression']; $class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable); $field = $pathExpression->field; if ($field === null) { $field = $pathExpression->field = $class->identifier[0]; } // Check if field or association exists if (! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) { $this->semanticalError( 'Class ' . $class->name . ' has no field or association named ' . $field, $deferredItem['token'] ); } $fieldType = AST\PathExpression::TYPE_STATE_FIELD; if (isset($class->associationMappings[$field])) { $assoc = $class->associationMappings[$field]; $fieldType = $assoc['type'] & ClassMetadata::TO_ONE ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION; } // Validate if PathExpression is one of the expected types $expectedType = $pathExpression->expectedType; if (! ($expectedType & $fieldType)) { // We need to recognize which was expected type(s) $expectedStringTypes = []; // Validate state field type if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { $expectedStringTypes[] = 'StateFieldPathExpression'; } // Validate single valued association (*-to-one) if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'SingleValuedAssociationField'; } // Validate single valued association (*-to-many) if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { $expectedStringTypes[] = 'CollectionValuedAssociationField'; } // Build the error message $semanticalError = 'Invalid PathExpression. '; $semanticalError .= count($expectedStringTypes) === 1 ? 'Must be a ' . $expectedStringTypes[0] . '.' : implode(' or ', $expectedStringTypes) . ' expected.'; $this->semanticalError($semanticalError, $deferredItem['token']); } // We need to force the type in PathExpression $pathExpression->type = $fieldType; } } private function processRootEntityAliasSelected(): void { if (! count($this->identVariableExpressions)) { return; } foreach ($this->identVariableExpressions as $dqlAlias => $expr) { if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) { return; } } $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); } /** * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement * * @return AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement */ public function QueryLanguage() { $statement = null; $this->lexer->moveNext(); switch ($this->lexer->lookahead->type ?? null) { case Lexer::T_SELECT: $statement = $this->SelectStatement(); break; case Lexer::T_UPDATE: $statement = $this->UpdateStatement(); break; case Lexer::T_DELETE: $statement = $this->DeleteStatement(); break; default: $this->syntaxError('SELECT, UPDATE or DELETE'); break; } // Check for end of string if ($this->lexer->lookahead !== null) { $this->syntaxError('end of string'); } return $statement; } /** * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return AST\SelectStatement */ public function SelectStatement() { $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); $selectStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $selectStatement->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; return $selectStatement; } /** * UpdateStatement ::= UpdateClause [WhereClause] * * @return AST\UpdateStatement */ public function UpdateStatement() { $updateStatement = new AST\UpdateStatement($this->UpdateClause()); $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $updateStatement; } /** * DeleteStatement ::= DeleteClause [WhereClause] * * @return AST\DeleteStatement */ public function DeleteStatement() { $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; return $deleteStatement; } /** * IdentificationVariable ::= identifier * * @return string */ public function IdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $identVariable = $this->lexer->token->value; $this->deferredIdentificationVariables[] = [ 'expression' => $identVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ]; return $identVariable; } /** * AliasIdentificationVariable = identifier * * @return string */ public function AliasIdentificationVariable() { $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $aliasIdentVariable = $this->lexer->token->value; $exists = isset($this->queryComponents[$aliasIdentVariable]); if ($exists) { $this->semanticalError( sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token ); } return $aliasIdentVariable; } /** * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier * * @return string */ public function AbstractSchemaName() { if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) { $this->match(Lexer::T_FULLY_QUALIFIED_NAME); assert($this->lexer->token !== null); return $this->lexer->token->value; } if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); return $this->lexer->token->value; } $this->match(Lexer::T_ALIASED_NAME); assert($this->lexer->token !== null); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.', $this->lexer->token->value ); [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token->value); return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; } /** * Validates an AbstractSchemaName, making sure the class exists. * * @param string $schemaName The name to validate. * * @throws QueryException if the name does not exist. */ private function validateAbstractSchemaName(string $schemaName): void { assert($this->lexer->token !== null); if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) { $this->semanticalError( sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token ); } } /** * AliasResultVariable ::= identifier * * @return string */ public function AliasResultVariable() { $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $resultVariable = $this->lexer->token->value; $exists = isset($this->queryComponents[$resultVariable]); if ($exists) { $this->semanticalError( sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token ); } return $resultVariable; } /** * ResultVariable ::= identifier * * @return string */ public function ResultVariable() { $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $resultVariable = $this->lexer->token->value; // Defer ResultVariable validation $this->deferredResultVariables[] = [ 'expression' => $resultVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ]; return $resultVariable; } /** * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) * * @return AST\JoinAssociationPathExpression */ public function JoinAssociationPathExpression() { $identVariable = $this->IdentificationVariable(); if (! isset($this->queryComponents[$identVariable])) { $this->semanticalError( 'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.' ); } $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $field = $this->lexer->token->value; // Validate association field $class = $this->getMetadataForDqlAlias($identVariable); if (! $class->hasAssociation($field)) { $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field); } return new AST\JoinAssociationPathExpression($identVariable, $field); } /** * Parses an arbitrary path expression and defers semantical validation * based on expected types. * * PathExpression ::= IdentificationVariable {"." identifier}* * * @param int $expectedTypes * @psalm-param int-mask-of<AST\PathExpression::TYPE_*> $expectedTypes * * @return AST\PathExpression */ public function PathExpression($expectedTypes) { $identVariable = $this->IdentificationVariable(); $field = null; assert($this->lexer->token !== null); if ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field = $this->lexer->token->value; while ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field .= '.' . $this->lexer->token->value; } } // Creating AST node $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); // Defer PathExpression validation if requested to be deferred $this->deferredPathExpressions[] = [ 'expression' => $pathExpr, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ]; return $pathExpr; } /** * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression * * @return AST\PathExpression */ public function AssociationPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION ); } /** * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression * * @return AST\PathExpression */ public function SingleValuedPathExpression() { return $this->PathExpression( AST\PathExpression::TYPE_STATE_FIELD | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION ); } /** * StateFieldPathExpression ::= IdentificationVariable "." StateField * * @return AST\PathExpression */ public function StateFieldPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); } /** * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField * * @return AST\PathExpression */ public function SingleValuedAssociationPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); } /** * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField * * @return AST\PathExpression */ public function CollectionValuedPathExpression() { return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); } /** * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} * * @return AST\SelectClause */ public function SelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); // Check for DISTINCT if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } // Process SelectExpressions (1..N) $selectExpressions = []; $selectExpressions[] = $this->SelectExpression(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $selectExpressions[] = $this->SelectExpression(); } return new AST\SelectClause($selectExpressions, $isDistinct); } /** * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression * * @return AST\SimpleSelectClause */ public function SimpleSelectClause() { $isDistinct = false; $this->match(Lexer::T_SELECT); if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct); } /** * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* * * @return AST\UpdateClause */ public function UpdateClause() { $this->match(Lexer::T_UPDATE); assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $abstractSchemaName = $this->AbstractSchemaName(); $this->validateAbstractSchemaName($abstractSchemaName); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $class = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = [ 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ]; $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; $this->match(Lexer::T_SET); $updateItems = []; $updateItems[] = $this->UpdateItem(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $updateItems[] = $this->UpdateItem(); } $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable; return $updateClause; } /** * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return AST\DeleteClause */ public function DeleteClause() { $this->match(Lexer::T_DELETE); if ($this->lexer->isNextToken(Lexer::T_FROM)) { $this->match(Lexer::T_FROM); } assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $abstractSchemaName = $this->AbstractSchemaName(); $this->validateAbstractSchemaName($abstractSchemaName); $deleteClause = new AST\DeleteClause($abstractSchemaName); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER) ? $this->AliasIdentificationVariable() : 'alias_should_have_been_set'; $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); // Building queryComponent $queryComponent = [ 'metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ]; $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return $deleteClause; } /** * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* * * @return AST\FromClause */ public function FromClause() { $this->match(Lexer::T_FROM); $identificationVariableDeclarations = []; $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); } return new AST\FromClause($identificationVariableDeclarations); } /** * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* * * @return AST\SubselectFromClause */ public function SubselectFromClause() { $this->match(Lexer::T_FROM); $identificationVariables = []; $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); } return new AST\SubselectFromClause($identificationVariables); } /** * WhereClause ::= "WHERE" ConditionalExpression * * @return AST\WhereClause */ public function WhereClause() { $this->match(Lexer::T_WHERE); return new AST\WhereClause($this->ConditionalExpression()); } /** * HavingClause ::= "HAVING" ConditionalExpression * * @return AST\HavingClause */ public function HavingClause() { $this->match(Lexer::T_HAVING); return new AST\HavingClause($this->ConditionalExpression()); } /** * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* * * @return AST\GroupByClause */ public function GroupByClause() { $this->match(Lexer::T_GROUP); $this->match(Lexer::T_BY); $groupByItems = [$this->GroupByItem()]; while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $groupByItems[] = $this->GroupByItem(); } return new AST\GroupByClause($groupByItems); } /** * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* * * @return AST\OrderByClause */ public function OrderByClause() { $this->match(Lexer::T_ORDER); $this->match(Lexer::T_BY); $orderByItems = []; $orderByItems[] = $this->OrderByItem(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $orderByItems[] = $this->OrderByItem(); } return new AST\OrderByClause($orderByItems); } /** * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] * * @return AST\Subselect */ public function Subselect() { // Increase query nesting level $this->nestingLevel++; $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); $subselect->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; $subselect->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; // Decrease query nesting level $this->nestingLevel--; return $subselect; } /** * UpdateItem ::= SingleValuedPathExpression "=" NewValue * * @return AST\UpdateItem */ public function UpdateItem() { $pathExpr = $this->SingleValuedPathExpression(); $this->match(Lexer::T_EQUALS); return new AST\UpdateItem($pathExpr, $this->NewValue()); } /** * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression * * @return string|AST\PathExpression */ public function GroupByItem() { // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->lexer->glimpse(); if ($glimpse !== null && $glimpse->type === Lexer::T_DOT) { return $this->SingleValuedPathExpression(); } assert($this->lexer->lookahead !== null); // Still need to decide between IdentificationVariable or ResultVariable $lookaheadValue = $this->lexer->lookahead->value; if (! isset($this->queryComponents[$lookaheadValue])) { $this->semanticalError('Cannot group by undefined identification or result variable.'); } return isset($this->queryComponents[$lookaheadValue]['metadata']) ? $this->IdentificationVariable() : $this->ResultVariable(); } /** * OrderByItem ::= ( * SimpleArithmeticExpression | SingleValuedPathExpression | CaseExpression | * ScalarExpression | ResultVariable | FunctionDeclaration * ) ["ASC" | "DESC"] * * @return AST\OrderByItem */ public function OrderByItem() { $this->lexer->peek(); // lookahead => '.' $this->lexer->peek(); // lookahead => token after '.' $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' $this->lexer->resetPeek(); $glimpse = $this->lexer->glimpse(); assert($this->lexer->lookahead !== null); switch (true) { case $this->isMathOperator($peek): $expr = $this->SimpleArithmeticExpression(); break; case $glimpse !== null && $glimpse->type === Lexer::T_DOT: $expr = $this->SingleValuedPathExpression(); break; case $this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()): $expr = $this->ScalarExpression(); break; case $this->lexer->lookahead->type === Lexer::T_CASE: $expr = $this->CaseExpression(); break; case $this->isFunction(): $expr = $this->FunctionDeclaration(); break; default: $expr = $this->ResultVariable(); break; } $type = 'ASC'; $item = new AST\OrderByItem($expr); switch (true) { case $this->lexer->isNextToken(Lexer::T_DESC): $this->match(Lexer::T_DESC); $type = 'DESC'; break; case $this->lexer->isNextToken(Lexer::T_ASC): $this->match(Lexer::T_ASC); break; default: // Do nothing } $item->type = $type; return $item; } /** * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | * EnumPrimary | SimpleEntityExpression | "NULL" * * NOTE: Since it is not possible to correctly recognize individual types, here is the full * grammar that needs to be supported: * * NewValue ::= SimpleArithmeticExpression | "NULL" * * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression * * @return AST\ArithmeticExpression|AST\InputParameter|null */ public function NewValue() { if ($this->lexer->isNextToken(Lexer::T_NULL)) { $this->match(Lexer::T_NULL); return null; } if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); assert($this->lexer->token !== null); return new AST\InputParameter($this->lexer->token->value); } return $this->ArithmeticExpression(); } /** * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* * * @return AST\IdentificationVariableDeclaration */ public function IdentificationVariableDeclaration() { $joins = []; $rangeVariableDeclaration = $this->RangeVariableDeclaration(); $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $rangeVariableDeclaration->isRoot = true; while ( $this->lexer->isNextToken(Lexer::T_LEFT) || $this->lexer->isNextToken(Lexer::T_INNER) || $this->lexer->isNextToken(Lexer::T_JOIN) ) { $joins[] = $this->Join(); } return new AST\IdentificationVariableDeclaration( $rangeVariableDeclaration, $indexBy, $joins ); } /** * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration * * {Internal note: WARNING: Solution is harder than a bare implementation. * Desired EBNF support: * * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) * * It demands that entire SQL generation to become programmatical. This is * needed because association based subselect requires "WHERE" conditional * expressions to be injected, but there is no scope to do that. Only scope * accessible is "FROM", prohibiting an easy implementation without larger * changes.} * * @return AST\IdentificationVariableDeclaration */ public function SubselectIdentificationVariableDeclaration() { /* NOT YET IMPLEMENTED! $glimpse = $this->lexer->glimpse(); if ($glimpse->type == Lexer::T_DOT) { $associationPathExpression = $this->AssociationPathExpression(); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $identificationVariable = $associationPathExpression->identificationVariable; $field = $associationPathExpression->associationField; $class = $this->queryComponents[$identificationVariable]['metadata']; $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']); // Building queryComponent $joinQueryComponent = array( 'metadata' => $targetClass, 'parent' => $identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->lookahead ); $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; return new AST\SubselectIdentificationVariableDeclaration( $associationPathExpression, $aliasIdentificationVariable ); } */ return $this->IdentificationVariableDeclaration(); } /** * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" * (JoinAssociationDeclaration | RangeVariableDeclaration) * ["WITH" ConditionalExpression] * * @return AST\Join */ public function Join() { // Check Join type $joinType = AST\Join::JOIN_TYPE_INNER; switch (true) { case $this->lexer->isNextToken(Lexer::T_LEFT): $this->match(Lexer::T_LEFT); $joinType = AST\Join::JOIN_TYPE_LEFT; // Possible LEFT OUTER join if ($this->lexer->isNextToken(Lexer::T_OUTER)) { $this->match(Lexer::T_OUTER); $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; } break; case $this->lexer->isNextToken(Lexer::T_INNER): $this->match(Lexer::T_INNER); break; default: // Do nothing } $this->match(Lexer::T_JOIN); $next = $this->lexer->glimpse(); assert($next !== null); $joinDeclaration = $next->type === Lexer::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH); $join = new AST\Join($joinType, $joinDeclaration); // Describe non-root join declaration if ($joinDeclaration instanceof AST\RangeVariableDeclaration) { $joinDeclaration->isRoot = false; } // Check for ad-hoc Join conditions if ($adhocConditions) { $this->match(Lexer::T_WITH); $join->conditionalExpression = $this->ConditionalExpression(); } return $join; } /** * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable * * @return AST\RangeVariableDeclaration * * @throws QueryException */ public function RangeVariableDeclaration() { if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()->type === Lexer::T_SELECT) { $this->semanticalError('Subquery is not supported here', $this->lexer->token); } $abstractSchemaName = $this->AbstractSchemaName(); $this->validateAbstractSchemaName($abstractSchemaName); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $classMetadata = $this->em->getClassMetadata($abstractSchemaName); // Building queryComponent $queryComponent = [ 'metadata' => $classMetadata, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ]; $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); } /** * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] * * @return AST\JoinAssociationDeclaration */ public function JoinAssociationDeclaration() { $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } assert($this->lexer->lookahead !== null); $aliasIdentificationVariable = $this->AliasIdentificationVariable(); $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; $identificationVariable = $joinAssociationPathExpression->identificationVariable; $field = $joinAssociationPathExpression->associationField; $class = $this->getMetadataForDqlAlias($identificationVariable); $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']); // Building queryComponent $joinQueryComponent = [ 'metadata' => $targetClass, 'parent' => $joinAssociationPathExpression->identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->lookahead, ]; $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); } /** * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" * * @return AST\PartialObjectExpression */ public function PartialObjectExpression() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8471', 'PARTIAL syntax in DQL is deprecated.' ); $this->match(Lexer::T_PARTIAL); $partialFieldSet = []; $identificationVariable = $this->IdentificationVariable(); $this->match(Lexer::T_DOT); $this->match(Lexer::T_OPEN_CURLY_BRACE); $this->match(Lexer::T_IDENTIFIER); assert($this->lexer->token !== null); $field = $this->lexer->token->value; // First field in partial expression might be embeddable property while ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field .= '.' . $this->lexer->token->value; } $partialFieldSet[] = $field; while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $this->match(Lexer::T_IDENTIFIER); $field = $this->lexer->token->value; while ($this->lexer->isNextToken(Lexer::T_DOT)) { $this->match(Lexer::T_DOT); $this->match(Lexer::T_IDENTIFIER); $field .= '.' . $this->lexer->token->value; } $partialFieldSet[] = $field; } $this->match(Lexer::T_CLOSE_CURLY_BRACE); $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); // Defer PartialObjectExpression validation $this->deferredPartialObjectExpressions[] = [ 'expression' => $partialObjectExpression, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token, ]; return $partialObjectExpression; } /** * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")" * * @return AST\NewObjectExpression */ public function NewObjectExpression() { $this->match(Lexer::T_NEW); $className = $this->AbstractSchemaName(); // note that this is not yet validated $token = $this->lexer->token; $this->match(Lexer::T_OPEN_PARENTHESIS); $args[] = $this->NewObjectArg(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $args[] = $this->NewObjectArg(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); $expression = new AST\NewObjectExpression($className, $args); // Defer NewObjectExpression validation $this->deferredNewObjectExpressions[] = [ 'token' => $token, 'expression' => $expression, 'nestingLevel' => $this->nestingLevel, ]; return $expression; } /** * NewObjectArg ::= ScalarExpression | "(" Subselect ")" * * @return mixed */ public function NewObjectArg() { assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $peek = $this->lexer->glimpse(); assert($peek !== null); if ($token->type === Lexer::T_OPEN_PARENTHESIS && $peek->type === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expression; } return $this->ScalarExpression(); } /** * IndexBy ::= "INDEX" "BY" SingleValuedPathExpression * * @return AST\IndexBy */ public function IndexBy() { $this->match(Lexer::T_INDEX); $this->match(Lexer::T_BY); $pathExpr = $this->SingleValuedPathExpression(); // Add the INDEX BY info to the query component $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; return new AST\IndexBy($pathExpr); } /** * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | * StateFieldPathExpression | BooleanPrimary | CaseExpression | * InstanceOfExpression * * @return mixed One of the possible expressions or subexpressions. */ public function ScalarExpression() { assert($this->lexer->token !== null); assert($this->lexer->lookahead !== null); $lookahead = $this->lexer->lookahead->type; $peek = $this->lexer->glimpse(); switch (true) { case $lookahead === Lexer::T_INTEGER: case $lookahead === Lexer::T_FLOAT: // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 ) case $lookahead === Lexer::T_MINUS: case $lookahead === Lexer::T_PLUS: return $this->SimpleArithmeticExpression(); case $lookahead === Lexer::T_STRING: return $this->StringPrimary(); case $lookahead === Lexer::T_TRUE: case $lookahead === Lexer::T_FALSE: $this->match($lookahead); return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value); case $lookahead === Lexer::T_INPUT_PARAMETER: switch (true) { case $this->isMathOperator($peek): // :param + u.value return $this->SimpleArithmeticExpression(); default: return $this->InputParameter(); } case $lookahead === Lexer::T_CASE: case $lookahead === Lexer::T_COALESCE: case $lookahead === Lexer::T_NULLIF: // Since NULLIF and COALESCE can be identified as a function, // we need to check these before checking for FunctionDeclaration return $this->CaseExpression(); case $lookahead === Lexer::T_OPEN_PARENTHESIS: return $this->SimpleArithmeticExpression(); // this check must be done before checking for a filed path expression case $this->isFunction(): $this->lexer->peek(); // "(" switch (true) { case $this->isMathOperator($this->peekBeyondClosingParenthesis()): // SUM(u.id) + COUNT(u.id) return $this->SimpleArithmeticExpression(); default: // IDENTITY(u) return $this->FunctionDeclaration(); } break; // it is no function, so it must be a field path case $lookahead === Lexer::T_IDENTIFIER: $this->lexer->peek(); // lookahead => '.' $this->lexer->peek(); // lookahead => token after '.' $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' $this->lexer->resetPeek(); if ($this->isMathOperator($peek)) { return $this->SimpleArithmeticExpression(); } return $this->StateFieldPathExpression(); default: $this->syntaxError(); } } /** * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @return mixed One of the possible expressions or subexpressions. */ public function CaseExpression() { assert($this->lexer->lookahead !== null); $lookahead = $this->lexer->lookahead->type; switch ($lookahead) { case Lexer::T_NULLIF: return $this->NullIfExpression(); case Lexer::T_COALESCE: return $this->CoalesceExpression(); case Lexer::T_CASE: $this->lexer->resetPeek(); $peek = $this->lexer->peek(); assert($peek !== null); if ($peek->type === Lexer::T_WHEN) { return $this->GeneralCaseExpression(); } return $this->SimpleCaseExpression(); default: // Do nothing break; } $this->syntaxError(); } /** * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" * * @return AST\CoalesceExpression */ public function CoalesceExpression() { $this->match(Lexer::T_COALESCE); $this->match(Lexer::T_OPEN_PARENTHESIS); // Process ScalarExpressions (1..N) $scalarExpressions = []; $scalarExpressions[] = $this->ScalarExpression(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $scalarExpressions[] = $this->ScalarExpression(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\CoalesceExpression($scalarExpressions); } /** * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" * * @return AST\NullIfExpression */ public function NullIfExpression() { $this->match(Lexer::T_NULLIF); $this->match(Lexer::T_OPEN_PARENTHESIS); $firstExpression = $this->ScalarExpression(); $this->match(Lexer::T_COMMA); $secondExpression = $this->ScalarExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\NullIfExpression($firstExpression, $secondExpression); } /** * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" * * @return AST\GeneralCaseExpression */ public function GeneralCaseExpression() { $this->match(Lexer::T_CASE); // Process WhenClause (1..N) $whenClauses = []; do { $whenClauses[] = $this->WhenClause(); } while ($this->lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); } /** * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator * * @return AST\SimpleCaseExpression */ public function SimpleCaseExpression() { $this->match(Lexer::T_CASE); $caseOperand = $this->StateFieldPathExpression(); // Process SimpleWhenClause (1..N) $simpleWhenClauses = []; do { $simpleWhenClauses[] = $this->SimpleWhenClause(); } while ($this->lexer->isNextToken(Lexer::T_WHEN)); $this->match(Lexer::T_ELSE); $scalarExpression = $this->ScalarExpression(); $this->match(Lexer::T_END); return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); } /** * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression * * @return AST\WhenClause */ public function WhenClause() { $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_THEN); return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); } /** * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression * * @return AST\SimpleWhenClause */ public function SimpleWhenClause() { $this->match(Lexer::T_WHEN); $conditionalExpression = $this->ScalarExpression(); $this->match(Lexer::T_THEN); return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); } /** * SelectExpression ::= ( * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | * PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression * ) [["AS"] ["HIDDEN"] AliasResultVariable] * * @return AST\SelectExpression */ public function SelectExpression() { assert($this->lexer->lookahead !== null); $expression = null; $identVariable = null; $peek = $this->lexer->glimpse(); $lookaheadType = $this->lexer->lookahead->type; assert($peek !== null); switch (true) { // ScalarExpression (u.name) case $lookaheadType === Lexer::T_IDENTIFIER && $peek->type === Lexer::T_DOT: $expression = $this->ScalarExpression(); break; // IdentificationVariable (u) case $lookaheadType === Lexer::T_IDENTIFIER && $peek->type !== Lexer::T_OPEN_PARENTHESIS: $expression = $identVariable = $this->IdentificationVariable(); break; // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) case $lookaheadType === Lexer::T_CASE: case $lookaheadType === Lexer::T_COALESCE: case $lookaheadType === Lexer::T_NULLIF: $expression = $this->CaseExpression(); break; // DQL Function (SUM(u.value) or SUM(u.value) + 1) case $this->isFunction(): $this->lexer->peek(); // "(" switch (true) { case $this->isMathOperator($this->peekBeyondClosingParenthesis()): // SUM(u.id) + COUNT(u.id) $expression = $this->ScalarExpression(); break; default: // IDENTITY(u) $expression = $this->FunctionDeclaration(); break; } break; // PartialObjectExpression (PARTIAL u.{id, name}) case $lookaheadType === Lexer::T_PARTIAL: $expression = $this->PartialObjectExpression(); $identVariable = $expression->identificationVariable; break; // Subselect case $lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek->type === Lexer::T_SELECT: $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); break; // Shortcut: ScalarExpression => SimpleArithmeticExpression case $lookaheadType === Lexer::T_OPEN_PARENTHESIS: case $lookaheadType === Lexer::T_INTEGER: case $lookaheadType === Lexer::T_STRING: case $lookaheadType === Lexer::T_FLOAT: // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) case $lookaheadType === Lexer::T_MINUS: case $lookaheadType === Lexer::T_PLUS: $expression = $this->SimpleArithmeticExpression(); break; // NewObjectExpression (New ClassName(id, name)) case $lookaheadType === Lexer::T_NEW: $expression = $this->NewObjectExpression(); break; default: $this->syntaxError( 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', $this->lexer->lookahead ); } // [["AS"] ["HIDDEN"] AliasResultVariable] $mustHaveAliasResultVariable = false; if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); $mustHaveAliasResultVariable = true; } $hiddenAliasResultVariable = false; if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) { $this->match(Lexer::T_HIDDEN); $hiddenAliasResultVariable = true; } $aliasResultVariable = null; if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { assert($expression instanceof AST\Node || is_string($expression)); $token = $this->lexer->lookahead; $aliasResultVariable = $this->AliasResultVariable(); // Include AliasResultVariable in query components. $this->queryComponents[$aliasResultVariable] = [ 'resultVariable' => $expression, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ]; } // AST $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); if ($identVariable) { $this->identVariableExpressions[$identVariable] = $expr; } return $expr; } /** * SimpleSelectExpression ::= ( * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | * AggregateExpression | "(" Subselect ")" | ScalarExpression * ) [["AS"] AliasResultVariable] * * @return AST\SimpleSelectExpression */ public function SimpleSelectExpression() { assert($this->lexer->lookahead !== null); $peek = $this->lexer->glimpse(); assert($peek !== null); switch ($this->lexer->lookahead->type) { case Lexer::T_IDENTIFIER: switch (true) { case $peek->type === Lexer::T_DOT: $expression = $this->StateFieldPathExpression(); return new AST\SimpleSelectExpression($expression); case $peek->type !== Lexer::T_OPEN_PARENTHESIS: $expression = $this->IdentificationVariable(); return new AST\SimpleSelectExpression($expression); case $this->isFunction(): // SUM(u.id) + COUNT(u.id) if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { return new AST\SimpleSelectExpression($this->ScalarExpression()); } // COUNT(u.id) if ($this->isAggregateFunction($this->lexer->lookahead->type)) { return new AST\SimpleSelectExpression($this->AggregateExpression()); } // IDENTITY(u) return new AST\SimpleSelectExpression($this->FunctionDeclaration()); default: // Do nothing } break; case Lexer::T_OPEN_PARENTHESIS: if ($peek->type !== Lexer::T_SELECT) { // Shortcut: ScalarExpression => SimpleArithmeticExpression $expression = $this->SimpleArithmeticExpression(); return new AST\SimpleSelectExpression($expression); } // Subselect $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\SimpleSelectExpression($expression); default: // Do nothing } $this->lexer->peek(); $expression = $this->ScalarExpression(); $expr = new AST\SimpleSelectExpression($expression); if ($this->lexer->isNextToken(Lexer::T_AS)) { $this->match(Lexer::T_AS); } if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { $token = $this->lexer->lookahead; $resultVariable = $this->AliasResultVariable(); $expr->fieldIdentificationVariable = $resultVariable; // Include AliasResultVariable in query components. $this->queryComponents[$resultVariable] = [ 'resultvariable' => $expr, 'nestingLevel' => $this->nestingLevel, 'token' => $token, ]; } return $expr; } /** * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* * * @return AST\ConditionalExpression|AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm */ public function ConditionalExpression() { $conditionalTerms = []; $conditionalTerms[] = $this->ConditionalTerm(); while ($this->lexer->isNextToken(Lexer::T_OR)) { $this->match(Lexer::T_OR); $conditionalTerms[] = $this->ConditionalTerm(); } // Phase 1 AST optimization: Prevent AST\ConditionalExpression // if only one AST\ConditionalTerm is defined if (count($conditionalTerms) === 1) { return $conditionalTerms[0]; } return new AST\ConditionalExpression($conditionalTerms); } /** * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* * * @return AST\ConditionalFactor|AST\ConditionalPrimary|AST\ConditionalTerm */ public function ConditionalTerm() { $conditionalFactors = []; $conditionalFactors[] = $this->ConditionalFactor(); while ($this->lexer->isNextToken(Lexer::T_AND)) { $this->match(Lexer::T_AND); $conditionalFactors[] = $this->ConditionalFactor(); } // Phase 1 AST optimization: Prevent AST\ConditionalTerm // if only one AST\ConditionalFactor is defined if (count($conditionalFactors) === 1) { return $conditionalFactors[0]; } return new AST\ConditionalTerm($conditionalFactors); } /** * ConditionalFactor ::= ["NOT"] ConditionalPrimary * * @return AST\ConditionalFactor|AST\ConditionalPrimary */ public function ConditionalFactor() { $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $conditionalPrimary = $this->ConditionalPrimary(); // Phase 1 AST optimization: Prevent AST\ConditionalFactor // if only one AST\ConditionalPrimary is defined if (! $not) { return $conditionalPrimary; } return new AST\ConditionalFactor($conditionalPrimary, $not); } /** * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" * * @return AST\ConditionalPrimary */ public function ConditionalPrimary() { $condPrimary = new AST\ConditionalPrimary(); if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } // Peek beyond the matching closing parenthesis ')' $peek = $this->peekBeyondClosingParenthesis(); if ( $peek !== null && ( in_array($peek->value, ['=', '<', '<=', '<>', '>', '>=', '!='], true) || in_array($peek->type, [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS], true) || $this->isMathOperator($peek) ) ) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); return $condPrimary; } $this->match(Lexer::T_OPEN_PARENTHESIS); $condPrimary->conditionalExpression = $this->ConditionalExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $condPrimary; } /** * SimpleConditionalExpression ::= * ComparisonExpression | BetweenExpression | LikeExpression | * InExpression | NullComparisonExpression | ExistsExpression | * EmptyCollectionComparisonExpression | CollectionMemberExpression | * InstanceOfExpression * * @return AST\BetweenExpression| * AST\CollectionMemberExpression| * AST\ComparisonExpression| * AST\EmptyCollectionComparisonExpression| * AST\ExistsExpression| * AST\InExpression| * AST\InstanceOfExpression| * AST\LikeExpression| * AST\NullComparisonExpression */ public function SimpleConditionalExpression() { assert($this->lexer->lookahead !== null); if ($this->lexer->isNextToken(Lexer::T_EXISTS)) { return $this->ExistsExpression(); } $token = $this->lexer->lookahead; $peek = $this->lexer->glimpse(); $lookahead = $token; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $token = $this->lexer->glimpse(); } assert($token !== null); assert($peek !== null); if ($token->type === Lexer::T_IDENTIFIER || $token->type === Lexer::T_INPUT_PARAMETER || $this->isFunction()) { // Peek beyond the matching closing parenthesis. $beyond = $this->lexer->peek(); switch ($peek->value) { case '(': // Peeks beyond the matched closing parenthesis. $token = $this->peekBeyondClosingParenthesis(false); assert($token !== null); if ($token->type === Lexer::T_NOT) { $token = $this->lexer->peek(); assert($token !== null); } if ($token->type === Lexer::T_IS) { $lookahead = $this->lexer->peek(); } break; default: // Peek beyond the PathExpression or InputParameter. $token = $beyond; while ($token->value === '.') { $this->lexer->peek(); $token = $this->lexer->peek(); assert($token !== null); } // Also peek beyond a NOT if there is one. assert($token !== null); if ($token->type === Lexer::T_NOT) { $token = $this->lexer->peek(); assert($token !== null); } // We need to go even further in case of IS (differentiate between NULL and EMPTY) $lookahead = $this->lexer->peek(); } assert($lookahead !== null); // Also peek beyond a NOT if there is one. if ($lookahead->type === Lexer::T_NOT) { $lookahead = $this->lexer->peek(); } $this->lexer->resetPeek(); } if ($token->type === Lexer::T_BETWEEN) { return $this->BetweenExpression(); } if ($token->type === Lexer::T_LIKE) { return $this->LikeExpression(); } if ($token->type === Lexer::T_IN) { return $this->InExpression(); } if ($token->type === Lexer::T_INSTANCE) { return $this->InstanceOfExpression(); } if ($token->type === Lexer::T_MEMBER) { return $this->CollectionMemberExpression(); } assert($lookahead !== null); if ($token->type === Lexer::T_IS && $lookahead->type === Lexer::T_NULL) { return $this->NullComparisonExpression(); } if ($token->type === Lexer::T_IS && $lookahead->type === Lexer::T_EMPTY) { return $this->EmptyCollectionComparisonExpression(); } return $this->ComparisonExpression(); } /** * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" * * @return AST\EmptyCollectionComparisonExpression */ public function EmptyCollectionComparisonExpression() { $pathExpression = $this->CollectionValuedPathExpression(); $this->match(Lexer::T_IS); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_EMPTY); return new AST\EmptyCollectionComparisonExpression( $pathExpression, $not ); } /** * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression * * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return AST\CollectionMemberExpression */ public function CollectionMemberExpression() { $not = false; $entityExpr = $this->EntityExpression(); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_MEMBER); if ($this->lexer->isNextToken(Lexer::T_OF)) { $this->match(Lexer::T_OF); } return new AST\CollectionMemberExpression( $entityExpr, $this->CollectionValuedPathExpression(), $not ); } /** * Literal ::= string | char | integer | float | boolean * * @return AST\Literal */ public function Literal() { assert($this->lexer->lookahead !== null); assert($this->lexer->token !== null); switch ($this->lexer->lookahead->type) { case Lexer::T_STRING: $this->match(Lexer::T_STRING); return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); case Lexer::T_INTEGER: case Lexer::T_FLOAT: $this->match( $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT ); return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token->value); case Lexer::T_TRUE: case Lexer::T_FALSE: $this->match( $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE ); return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value); default: $this->syntaxError('Literal'); } } /** * InParameter ::= ArithmeticExpression | InputParameter * * @return AST\InputParameter|AST\ArithmeticExpression */ public function InParameter() { assert($this->lexer->lookahead !== null); if ($this->lexer->lookahead->type === Lexer::T_INPUT_PARAMETER) { return $this->InputParameter(); } return $this->ArithmeticExpression(); } /** * InputParameter ::= PositionalParameter | NamedParameter * * @return AST\InputParameter */ public function InputParameter() { $this->match(Lexer::T_INPUT_PARAMETER); assert($this->lexer->token !== null); return new AST\InputParameter($this->lexer->token->value); } /** * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" * * @return AST\ArithmeticExpression */ public function ArithmeticExpression() { $expr = new AST\ArithmeticExpression(); if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $peek = $this->lexer->glimpse(); assert($peek !== null); if ($peek->type === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr->subselect = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } } $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression(); return $expr; } /** * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* * * @return AST\SimpleArithmeticExpression|AST\ArithmeticTerm */ public function SimpleArithmeticExpression() { $terms = []; $terms[] = $this->ArithmeticTerm(); while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match($isPlus ? Lexer::T_PLUS : Lexer::T_MINUS); assert($this->lexer->token !== null); $terms[] = $this->lexer->token->value; $terms[] = $this->ArithmeticTerm(); } // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression // if only one AST\ArithmeticTerm is defined if (count($terms) === 1) { return $terms[0]; } return new AST\SimpleArithmeticExpression($terms); } /** * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* * * @return AST\ArithmeticTerm */ public function ArithmeticTerm() { $factors = []; $factors[] = $this->ArithmeticFactor(); while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) { $this->match($isMult ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE); assert($this->lexer->token !== null); $factors[] = $this->lexer->token->value; $factors[] = $this->ArithmeticFactor(); } // Phase 1 AST optimization: Prevent AST\ArithmeticTerm // if only one AST\ArithmeticFactor is defined if (count($factors) === 1) { return $factors[0]; } return new AST\ArithmeticTerm($factors); } /** * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary * * @return AST\ArithmeticFactor */ public function ArithmeticFactor() { $sign = null; $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS); if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) { $this->match($isPlus ? Lexer::T_PLUS : Lexer::T_MINUS); $sign = $isPlus; } $primary = $this->ArithmeticPrimary(); // Phase 1 AST optimization: Prevent AST\ArithmeticFactor // if only one AST\ArithmeticPrimary is defined if ($sign === null) { return $primary; } return new AST\ArithmeticFactor($primary, $sign); } /** * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable * | InputParameter | CaseExpression * * @return AST\Node|string */ public function ArithmeticPrimary() { if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\ParenthesisExpression($expr); } assert($this->lexer->lookahead !== null); switch ($this->lexer->lookahead->type) { case Lexer::T_COALESCE: case Lexer::T_NULLIF: case Lexer::T_CASE: return $this->CaseExpression(); case Lexer::T_IDENTIFIER: $peek = $this->lexer->glimpse(); if ($peek !== null && $peek->value === '(') { return $this->FunctionDeclaration(); } if ($peek !== null && $peek->value === '.') { return $this->SingleValuedPathExpression(); } if (isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) { return $this->ResultVariable(); } return $this->StateFieldPathExpression(); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); default: $peek = $this->lexer->glimpse(); if ($peek !== null && $peek->value === '(') { return $this->FunctionDeclaration(); } return $this->Literal(); } } /** * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")" * * @return AST\Subselect|AST\Node|string */ public function StringExpression() { $peek = $this->lexer->glimpse(); assert($peek !== null); // Subselect if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek->type === Lexer::T_SELECT) { $this->match(Lexer::T_OPEN_PARENTHESIS); $expr = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return $expr; } assert($this->lexer->lookahead !== null); // ResultVariable (string) if ( $this->lexer->isNextToken(Lexer::T_IDENTIFIER) && isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable']) ) { return $this->ResultVariable(); } return $this->StringPrimary(); } /** * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression * * @return AST\Node */ public function StringPrimary() { assert($this->lexer->lookahead !== null); $lookaheadType = $this->lexer->lookahead->type; switch ($lookaheadType) { case Lexer::T_IDENTIFIER: $peek = $this->lexer->glimpse(); assert($peek !== null); if ($peek->value === '.') { return $this->StateFieldPathExpression(); } if ($peek->value === '(') { // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions. return $this->FunctionDeclaration(); } $this->syntaxError("'.' or '('"); break; case Lexer::T_STRING: $this->match(Lexer::T_STRING); assert($this->lexer->token !== null); return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); case Lexer::T_INPUT_PARAMETER: return $this->InputParameter(); case Lexer::T_CASE: case Lexer::T_COALESCE: case Lexer::T_NULLIF: return $this->CaseExpression(); default: assert($lookaheadType !== null); if ($this->isAggregateFunction($lookaheadType)) { return $this->AggregateExpression(); } } $this->syntaxError( 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression' ); } /** * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression * * @return AST\InputParameter|AST\PathExpression */ public function EntityExpression() { $glimpse = $this->lexer->glimpse(); assert($glimpse !== null); if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse->value === '.') { return $this->SingleValuedAssociationPathExpression(); } return $this->SimpleEntityExpression(); } /** * SimpleEntityExpression ::= IdentificationVariable | InputParameter * * @return AST\InputParameter|AST\PathExpression */ public function SimpleEntityExpression() { if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { return $this->InputParameter(); } return $this->StateFieldPathExpression(); } /** * AggregateExpression ::= * ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")" * * @return AST\AggregateExpression */ public function AggregateExpression() { assert($this->lexer->lookahead !== null); $lookaheadType = $this->lexer->lookahead->type; $isDistinct = false; if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM], true)) { $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); } $this->match($lookaheadType); assert($this->lexer->token !== null); $functionName = $this->lexer->token->value; $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { $this->match(Lexer::T_DISTINCT); $isDistinct = true; } $pathExp = $this->SimpleArithmeticExpression(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); } /** * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" * * @return AST\QuantifiedExpression */ public function QuantifiedExpression() { assert($this->lexer->lookahead !== null); $lookaheadType = $this->lexer->lookahead->type; $value = $this->lexer->lookahead->value; if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true)) { $this->syntaxError('ALL, ANY or SOME'); } $this->match($lookaheadType); $this->match(Lexer::T_OPEN_PARENTHESIS); $qExpr = new AST\QuantifiedExpression($this->Subselect()); $qExpr->type = $value; $this->match(Lexer::T_CLOSE_PARENTHESIS); return $qExpr; } /** * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression * * @return AST\BetweenExpression */ public function BetweenExpression() { $not = false; $arithExpr1 = $this->ArithmeticExpression(); if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_BETWEEN); $arithExpr2 = $this->ArithmeticExpression(); $this->match(Lexer::T_AND); $arithExpr3 = $this->ArithmeticExpression(); return new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3, $not); } /** * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) * * @return AST\ComparisonExpression */ public function ComparisonExpression() { $this->lexer->glimpse(); $leftExpr = $this->ArithmeticExpression(); $operator = $this->ComparisonOperator(); $rightExpr = $this->isNextAllAnySome() ? $this->QuantifiedExpression() : $this->ArithmeticExpression(); return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); } /** * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" * * @return AST\InListExpression|AST\InSubselectExpression */ public function InExpression() { $expression = $this->ArithmeticExpression(); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_IN); $this->match(Lexer::T_OPEN_PARENTHESIS); if ($this->lexer->isNextToken(Lexer::T_SELECT)) { $inExpression = new AST\InSubselectExpression( $expression, $this->Subselect(), $not ); } else { $literals = [$this->InParameter()]; while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $literals[] = $this->InParameter(); } $inExpression = new AST\InListExpression( $expression, $literals, $not ); } $this->match(Lexer::T_CLOSE_PARENTHESIS); return $inExpression; } /** * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") * * @return AST\InstanceOfExpression */ public function InstanceOfExpression() { $identificationVariable = $this->IdentificationVariable(); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_INSTANCE); $this->match(Lexer::T_OF); $exprValues = $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) ? $this->InstanceOfParameterList() : [$this->InstanceOfParameter()]; return new AST\InstanceOfExpression( $identificationVariable, $exprValues, $not ); } /** @return non-empty-list<AST\InputParameter|string> */ public function InstanceOfParameterList(): array { $this->match(Lexer::T_OPEN_PARENTHESIS); $exprValues = [$this->InstanceOfParameter()]; while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $exprValues[] = $this->InstanceOfParameter(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); return $exprValues; } /** * InstanceOfParameter ::= AbstractSchemaName | InputParameter * * @return AST\InputParameter|string */ public function InstanceOfParameter() { if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); assert($this->lexer->token !== null); return new AST\InputParameter($this->lexer->token->value); } $abstractSchemaName = $this->AbstractSchemaName(); $this->validateAbstractSchemaName($abstractSchemaName); return $abstractSchemaName; } /** * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] * * @return AST\LikeExpression */ public function LikeExpression() { $stringExpr = $this->StringExpression(); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_LIKE); if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { $this->match(Lexer::T_INPUT_PARAMETER); assert($this->lexer->token !== null); $stringPattern = new AST\InputParameter($this->lexer->token->value); } else { $stringPattern = $this->StringPrimary(); } $escapeChar = null; if ($this->lexer->lookahead !== null && $this->lexer->lookahead->type === Lexer::T_ESCAPE) { $this->match(Lexer::T_ESCAPE); $this->match(Lexer::T_STRING); assert($this->lexer->token !== null); $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token->value); } return new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar, $not); } /** * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL" * * @return AST\NullComparisonExpression */ public function NullComparisonExpression() { switch (true) { case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER): $this->match(Lexer::T_INPUT_PARAMETER); assert($this->lexer->token !== null); $expr = new AST\InputParameter($this->lexer->token->value); break; case $this->lexer->isNextToken(Lexer::T_NULLIF): $expr = $this->NullIfExpression(); break; case $this->lexer->isNextToken(Lexer::T_COALESCE): $expr = $this->CoalesceExpression(); break; case $this->isFunction(): $expr = $this->FunctionDeclaration(); break; default: // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->lexer->glimpse(); assert($glimpse !== null); if ($glimpse->type === Lexer::T_DOT) { $expr = $this->SingleValuedPathExpression(); // Leave switch statement break; } assert($this->lexer->lookahead !== null); $lookaheadValue = $this->lexer->lookahead->value; // Validate existing component if (! isset($this->queryComponents[$lookaheadValue])) { $this->semanticalError('Cannot add having condition on undefined result variable.'); } // Validate SingleValuedPathExpression (ie.: "product") if (isset($this->queryComponents[$lookaheadValue]['metadata'])) { $expr = $this->SingleValuedPathExpression(); break; } // Validating ResultVariable if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) { $this->semanticalError('Cannot add having condition on a non result variable.'); } $expr = $this->ResultVariable(); break; } $this->match(Lexer::T_IS); $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_NULL); return new AST\NullComparisonExpression($expr, $not); } /** * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" * * @return AST\ExistsExpression */ public function ExistsExpression() { $not = false; if ($this->lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); $not = true; } $this->match(Lexer::T_EXISTS); $this->match(Lexer::T_OPEN_PARENTHESIS); $subselect = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); return new AST\ExistsExpression($subselect, $not); } /** * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" * * @return string */ public function ComparisonOperator() { assert($this->lexer->lookahead !== null); switch ($this->lexer->lookahead->value) { case '=': $this->match(Lexer::T_EQUALS); return '='; case '<': $this->match(Lexer::T_LOWER_THAN); $operator = '<'; if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) { $this->match(Lexer::T_GREATER_THAN); $operator .= '>'; } return $operator; case '>': $this->match(Lexer::T_GREATER_THAN); $operator = '>'; if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { $this->match(Lexer::T_EQUALS); $operator .= '='; } return $operator; case '!': $this->match(Lexer::T_NEGATE); $this->match(Lexer::T_EQUALS); return '<>'; default: $this->syntaxError('=, <, <=, <>, >, >=, !='); } } /** * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime * * @return Functions\FunctionNode */ public function FunctionDeclaration() { assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $funcName = strtolower($token->value); $customFunctionDeclaration = $this->CustomFunctionDeclaration(); // Check for custom functions functions first! switch (true) { case $customFunctionDeclaration !== null: return $customFunctionDeclaration; case isset(self::$stringFunctions[$funcName]): return $this->FunctionsReturningStrings(); case isset(self::$numericFunctions[$funcName]): return $this->FunctionsReturningNumerics(); case isset(self::$datetimeFunctions[$funcName]): return $this->FunctionsReturningDatetime(); default: $this->syntaxError('known function', $token); } } /** * Helper function for FunctionDeclaration grammar rule. */ private function CustomFunctionDeclaration(): ?Functions\FunctionNode { assert($this->lexer->lookahead !== null); $token = $this->lexer->lookahead; $funcName = strtolower($token->value); // Check for custom functions afterwards $config = $this->em->getConfiguration(); switch (true) { case $config->getCustomStringFunction($funcName) !== null: return $this->CustomFunctionsReturningStrings(); case $config->getCustomNumericFunction($funcName) !== null: return $this->CustomFunctionsReturningNumerics(); case $config->getCustomDatetimeFunction($funcName) !== null: return $this->CustomFunctionsReturningDatetime(); default: return null; } } /** * FunctionsReturningNumerics ::= * "LENGTH" "(" StringPrimary ")" | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | * "ABS" "(" SimpleArithmeticExpression ")" | * "SQRT" "(" SimpleArithmeticExpression ")" | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "SIZE" "(" CollectionValuedPathExpression ")" | * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" * * @return Functions\FunctionNode */ public function FunctionsReturningNumerics() { assert($this->lexer->lookahead !== null); $funcNameLower = strtolower($this->lexer->lookahead->value); $funcClass = self::$numericFunctions[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** @return Functions\FunctionNode */ public function CustomFunctionsReturningNumerics() { assert($this->lexer->lookahead !== null); // getCustomNumericFunction is case-insensitive $functionName = strtolower($this->lexer->lookahead->value); $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName); assert($functionClass !== null); $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName); $function->parse($this); return $function; } /** * FunctionsReturningDateTime ::= * "CURRENT_DATE" | * "CURRENT_TIME" | * "CURRENT_TIMESTAMP" | * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | * "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" * * @return Functions\FunctionNode */ public function FunctionsReturningDatetime() { assert($this->lexer->lookahead !== null); $funcNameLower = strtolower($this->lexer->lookahead->value); $funcClass = self::$datetimeFunctions[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** @return Functions\FunctionNode */ public function CustomFunctionsReturningDatetime() { assert($this->lexer->lookahead !== null); // getCustomDatetimeFunction is case-insensitive $functionName = $this->lexer->lookahead->value; $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName); assert($functionClass !== null); $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName); $function->parse($this); return $function; } /** * FunctionsReturningStrings ::= * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | * "LOWER" "(" StringPrimary ")" | * "UPPER" "(" StringPrimary ")" | * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" * * @return Functions\FunctionNode */ public function FunctionsReturningStrings() { assert($this->lexer->lookahead !== null); $funcNameLower = strtolower($this->lexer->lookahead->value); $funcClass = self::$stringFunctions[$funcNameLower]; $function = new $funcClass($funcNameLower); $function->parse($this); return $function; } /** @return Functions\FunctionNode */ public function CustomFunctionsReturningStrings() { assert($this->lexer->lookahead !== null); // getCustomStringFunction is case-insensitive $functionName = $this->lexer->lookahead->value; $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName); assert($functionClass !== null); $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName); $function->parse($this); return $function; } private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata { if (! isset($this->queryComponents[$dqlAlias]['metadata'])) { throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); } return $this->queryComponents[$dqlAlias]['metadata']; } } orm/lib/Doctrine/ORM/Query/ParserResult.php 0000644 00000007270 15120025736 0014573 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; use function sprintf; /** * Encapsulates the resulting components from a DQL query parsing process that * can be serialized. * * @link http://www.doctrine-project.org */ class ParserResult { private const LEGACY_PROPERTY_MAPPING = [ 'sqlExecutor' => '_sqlExecutor', 'resultSetMapping' => '_resultSetMapping', 'parameterMappings' => '_parameterMappings', ]; /** * The SQL executor used for executing the SQL. * * @var AbstractSqlExecutor */ private $sqlExecutor; /** * The ResultSetMapping that describes how to map the SQL result set. * * @var ResultSetMapping */ private $resultSetMapping; /** * The mappings of DQL parameter names/positions to SQL parameter positions. * * @psalm-var array<string|int, list<int>> */ private $parameterMappings = []; /** * Initializes a new instance of the <tt>ParserResult</tt> class. * The new instance is initialized with an empty <tt>ResultSetMapping</tt>. */ public function __construct() { $this->resultSetMapping = new ResultSetMapping(); } /** * Gets the ResultSetMapping for the parsed query. * * @return ResultSetMapping The result set mapping of the parsed query */ public function getResultSetMapping() { return $this->resultSetMapping; } /** * Sets the ResultSetMapping of the parsed query. * * @return void */ public function setResultSetMapping(ResultSetMapping $rsm) { $this->resultSetMapping = $rsm; } /** * Sets the SQL executor that should be used for this ParserResult. * * @param AbstractSqlExecutor $executor * * @return void */ public function setSqlExecutor($executor) { $this->sqlExecutor = $executor; } /** * Gets the SQL executor used by this ParserResult. * * @return AbstractSqlExecutor */ public function getSqlExecutor() { return $this->sqlExecutor; } /** * Adds a DQL to SQL parameter mapping. One DQL parameter name/position can map to * several SQL parameter positions. * * @param string|int $dqlPosition * @param int $sqlPosition * * @return void */ public function addParameterMapping($dqlPosition, $sqlPosition) { $this->parameterMappings[$dqlPosition][] = $sqlPosition; } /** * Gets all DQL to SQL parameter mappings. * * @psalm-return array<int|string, list<int>> The parameter mappings. */ public function getParameterMappings() { return $this->parameterMappings; } /** * Gets the SQL parameter positions for a DQL parameter name/position. * * @param string|int $dqlPosition The name or position of the DQL parameter. * * @return int[] The positions of the corresponding SQL parameters. * @psalm-return list<int> */ public function getSqlParameterPositions($dqlPosition) { return $this->parameterMappings[$dqlPosition]; } public function __wakeup(): void { $this->__unserialize((array) $this); } /** @param array<string, mixed> $data */ public function __unserialize(array $data): void { foreach (self::LEGACY_PROPERTY_MAPPING as $property => $legacyProperty) { $this->$property = $data[sprintf("\0%s\0%s", self::class, $legacyProperty)] ?? $data[sprintf("\0%s\0%s", self::class, $property)] ?? $this->$property ?? null; } } } orm/lib/Doctrine/ORM/Query/Printer.php 0000644 00000003237 15120025736 0013562 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use function str_repeat; /** * A parse tree printer for Doctrine Query Language parser. * * @link http://www.phpdoctrine.org */ class Printer { /** * Current indentation level * * @var int */ protected $_indent = 0; /** * Defines whether parse tree is printed (default, false) or not (true). * * @var bool */ protected $_silent; /** * Constructs a new parse tree printer. * * @param bool $silent Parse tree will not be printed if true. */ public function __construct($silent = false) { $this->_silent = $silent; } /** * Prints an opening parenthesis followed by production name and increases * indentation level by one. * * This method is called before executing a production. * * @param string $name Production name. * * @return void */ public function startProduction($name) { $this->println('(' . $name); $this->_indent++; } /** * Decreases indentation level by one and prints a closing parenthesis. * * This method is called after executing a production. * * @return void */ public function endProduction() { $this->_indent--; $this->println(')'); } /** * Prints text indented with spaces depending on current indentation level. * * @param string $str The text. * * @return void */ public function println($str) { if (! $this->_silent) { echo str_repeat(' ', $this->_indent), $str, "\n"; } } } orm/lib/Doctrine/ORM/Query/QueryException.php 0000644 00000015651 15120025736 0015126 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\AST\PathExpression; use Exception; use Stringable; /** @psalm-import-type AssociationMapping from ClassMetadata */ class QueryException extends ORMException { /** * @param string $dql * * @return QueryException */ public static function dqlError($dql) { return new self($dql); } /** * @param string $message * @param Exception|null $previous * * @return QueryException */ public static function syntaxError($message, $previous = null) { return new self('[Syntax Error] ' . $message, 0, $previous); } /** * @param string $message * @param Exception|null $previous * * @return QueryException */ public static function semanticalError($message, $previous = null) { return new self('[Semantical Error] ' . $message, 0, $previous); } /** @return QueryException */ public static function invalidLockMode() { return new self('Invalid lock mode hint provided.'); } /** * @param string $expected * @param string $received * * @return QueryException */ public static function invalidParameterType($expected, $received) { return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.'); } /** * @param string $pos * * @return QueryException */ public static function invalidParameterPosition($pos) { return new self('Invalid parameter position: ' . $pos); } /** * @param int $expected * @param int $received * * @return QueryException */ public static function tooManyParameters($expected, $received) { return new self('Too many parameters: the query defines ' . $expected . ' parameters and you bound ' . $received); } /** * @param int $expected * @param int $received * * @return QueryException */ public static function tooFewParameters($expected, $received) { return new self('Too few parameters: the query defines ' . $expected . ' parameters but you only bound ' . $received); } /** * @param string $value * * @return QueryException */ public static function invalidParameterFormat($value) { return new self('Invalid parameter format, ' . $value . ' given, but :<name> or ?<num> expected.'); } /** * @param string $key * * @return QueryException */ public static function unknownParameter($key) { return new self('Invalid parameter: token ' . $key . ' is not defined in the query.'); } /** @return QueryException */ public static function parameterTypeMismatch() { return new self('DQL Query parameter and type numbers mismatch, but have to be exactly equal.'); } /** * @param PathExpression $pathExpr * * @return QueryException */ public static function invalidPathExpression($pathExpr) { return new self( "Invalid PathExpression '" . $pathExpr->identificationVariable . '.' . $pathExpr->field . "'." ); } /** * @param string|Stringable $literal * * @return QueryException */ public static function invalidLiteral($literal) { return new self("Invalid literal '" . $literal . "'"); } /** * @param string[] $assoc * @psalm-param AssociationMapping $assoc * * @return QueryException */ public static function iterateWithFetchJoinCollectionNotAllowed($assoc) { return new self( 'Invalid query operation: Not allowed to iterate over fetch join collections ' . 'in class ' . $assoc['sourceEntity'] . ' association ' . $assoc['fieldName'] ); } /** @return QueryException */ public static function partialObjectsAreDangerous() { return new self( 'Loading partial objects is dangerous. Fetch full objects or consider ' . 'using a different fetch mode. If you really want partial objects, ' . 'set the doctrine.forcePartialLoad query hint to TRUE.' ); } /** * @param string[] $assoc * @psalm-param array<string, string> $assoc * * @return QueryException */ public static function overwritingJoinConditionsNotYetSupported($assoc) { return new self( 'Unsupported query operation: It is not yet possible to overwrite the join ' . 'conditions in class ' . $assoc['sourceEntityName'] . ' association ' . $assoc['fieldName'] . '. ' . 'Use WITH to append additional join conditions to the association.' ); } /** @return QueryException */ public static function associationPathInverseSideNotSupported(PathExpression $pathExpr) { return new self( 'A single-valued association path expression to an inverse side is not supported in DQL queries. ' . 'Instead of "' . $pathExpr->identificationVariable . '.' . $pathExpr->field . '" use an explicit join.' ); } /** * @param string[] $assoc * @psalm-param AssociationMapping $assoc * * @return QueryException */ public static function iterateWithFetchJoinNotAllowed($assoc) { return new self( 'Iterate with fetch join in class ' . $assoc['sourceEntity'] . ' using association ' . $assoc['fieldName'] . ' not allowed.' ); } public static function iterateWithMixedResultNotAllowed(): QueryException { return new self('Iterating a query with mixed results (using scalars) is not supported.'); } /** @return QueryException */ public static function associationPathCompositeKeyNotSupported() { return new self( 'A single-valued association path expression to an entity with a composite primary ' . 'key is not supported. Explicitly name the components of the composite primary key ' . 'in the query.' ); } /** * @param string $className * @param string $rootClass * * @return QueryException */ public static function instanceOfUnrelatedClass($className, $rootClass) { return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " . 'inheritance hierarchy does not exists between these two classes.'); } /** * @param string $dqlAlias * * @return QueryException */ public static function invalidQueryComponent($dqlAlias) { return new self( "Invalid query component given for DQL alias '" . $dqlAlias . "', " . "requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys." ); } } orm/lib/Doctrine/ORM/Query/QueryExpressionVisitor.php 0000644 00000014301 15120025736 0016676 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Expr\Comparison; use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\ExpressionVisitor; use Doctrine\Common\Collections\Expr\Value; use RuntimeException; use function count; use function defined; use function str_replace; use function str_starts_with; /** * Converts Collection expressions to Query expressions. */ class QueryExpressionVisitor extends ExpressionVisitor { /** @var array<string,string> */ private static $operatorMap = [ Comparison::GT => Expr\Comparison::GT, Comparison::GTE => Expr\Comparison::GTE, Comparison::LT => Expr\Comparison::LT, Comparison::LTE => Expr\Comparison::LTE, ]; /** @var mixed[] */ private $queryAliases; /** @var Expr */ private $expr; /** @var list<mixed> */ private $parameters = []; /** @param mixed[] $queryAliases */ public function __construct($queryAliases) { $this->queryAliases = $queryAliases; $this->expr = new Expr(); } /** * Gets bound parameters. * Filled after {@link dispach()}. * * @return ArrayCollection<int, mixed> */ public function getParameters() { return new ArrayCollection($this->parameters); } /** * Clears parameters. * * @return void */ public function clearParameters() { $this->parameters = []; } /** * Converts Criteria expression to Query one based on static map. * * @param string $criteriaOperator * * @return string|null */ private static function convertComparisonOperator($criteriaOperator) { return self::$operatorMap[$criteriaOperator] ?? null; } /** * {@inheritDoc} */ public function walkCompositeExpression(CompositeExpression $expr) { $expressionList = []; foreach ($expr->getExpressionList() as $child) { $expressionList[] = $this->dispatch($child); } switch ($expr->getType()) { case CompositeExpression::TYPE_AND: return new Expr\Andx($expressionList); case CompositeExpression::TYPE_OR: return new Expr\Orx($expressionList); default: // Multiversion support for `doctrine/collections` before and after v2.1.0 if (defined(CompositeExpression::class . '::TYPE_NOT') && $expr->getType() === CompositeExpression::TYPE_NOT) { return $this->expr->not($expressionList[0]); } throw new RuntimeException('Unknown composite ' . $expr->getType()); } } /** * {@inheritDoc} */ public function walkComparison(Comparison $comparison) { if (! isset($this->queryAliases[0])) { throw new QueryException('No aliases are set before invoking walkComparison().'); } $field = $this->queryAliases[0] . '.' . $comparison->getField(); foreach ($this->queryAliases as $alias) { if (str_starts_with($comparison->getField() . '.', $alias . '.')) { $field = $comparison->getField(); break; } } $parameterName = str_replace('.', '_', $comparison->getField()); foreach ($this->parameters as $parameter) { if ($parameter->getName() === $parameterName) { $parameterName .= '_' . count($this->parameters); break; } } $parameter = new Parameter($parameterName, $this->walkValue($comparison->getValue())); $placeholder = ':' . $parameterName; switch ($comparison->getOperator()) { case Comparison::IN: $this->parameters[] = $parameter; return $this->expr->in($field, $placeholder); case Comparison::NIN: $this->parameters[] = $parameter; return $this->expr->notIn($field, $placeholder); case Comparison::EQ: case Comparison::IS: if ($this->walkValue($comparison->getValue()) === null) { return $this->expr->isNull($field); } $this->parameters[] = $parameter; return $this->expr->eq($field, $placeholder); case Comparison::NEQ: if ($this->walkValue($comparison->getValue()) === null) { return $this->expr->isNotNull($field); } $this->parameters[] = $parameter; return $this->expr->neq($field, $placeholder); case Comparison::CONTAINS: $parameter->setValue('%' . $parameter->getValue() . '%', $parameter->getType()); $this->parameters[] = $parameter; return $this->expr->like($field, $placeholder); case Comparison::MEMBER_OF: return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue()); case Comparison::STARTS_WITH: $parameter->setValue($parameter->getValue() . '%', $parameter->getType()); $this->parameters[] = $parameter; return $this->expr->like($field, $placeholder); case Comparison::ENDS_WITH: $parameter->setValue('%' . $parameter->getValue(), $parameter->getType()); $this->parameters[] = $parameter; return $this->expr->like($field, $placeholder); default: $operator = self::convertComparisonOperator($comparison->getOperator()); if ($operator) { $this->parameters[] = $parameter; return new Expr\Comparison( $field, $operator, $placeholder ); } throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator()); } } /** * {@inheritDoc} */ public function walkValue(Value $value) { return $value->getValue(); } } orm/lib/Doctrine/ORM/Query/ResultSetMapping.php 0000644 00000040643 15120025736 0015407 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use function array_merge; use function count; /** * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result. * * IMPORTANT NOTE: * The properties of this class are only public for fast internal READ access and to (drastically) * reduce the size of serialized instances for more effective caching due to better (un-)serialization * performance. * * <b>Users should use the public methods.</b> * * @todo Think about whether the number of lookup maps can be reduced. */ class ResultSetMapping { /** * Whether the result is mixed (contains scalar values together with field values). * * @ignore * @var bool */ public $isMixed = false; /** * Whether the result is a select statement. * * @ignore * @var bool */ public $isSelect = true; /** * Maps alias names to class names. * * @ignore * @psalm-var array<string, class-string> */ public $aliasMap = []; /** * Maps alias names to related association field names. * * @ignore * @psalm-var array<string, string> */ public $relationMap = []; /** * Maps alias names to parent alias names. * * @ignore * @psalm-var array<string, string> */ public $parentAliasMap = []; /** * Maps column names in the result set to field names for each class. * * @ignore * @psalm-var array<string, string> */ public $fieldMappings = []; /** * Maps column names in the result set to the alias/field name to use in the mapped result. * * @ignore * @psalm-var array<string, string|int> */ public $scalarMappings = []; /** * Maps scalar columns to enums * * @ignore * @psalm-var array<string, string> */ public $enumMappings = []; /** * Maps column names in the result set to the alias/field type to use in the mapped result. * * @ignore * @psalm-var array<string, string> */ public $typeMappings = []; /** * Maps entities in the result set to the alias name to use in the mapped result. * * @ignore * @psalm-var array<string, string|null> */ public $entityMappings = []; /** * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names. * * @ignore * @psalm-var array<string, string> */ public $metaMappings = []; /** * Maps column names in the result set to the alias they belong to. * * @ignore * @psalm-var array<string, string> */ public $columnOwnerMap = []; /** * List of columns in the result set that are used as discriminator columns. * * @ignore * @psalm-var array<string, string> */ public $discriminatorColumns = []; /** * Maps alias names to field names that should be used for indexing. * * @ignore * @psalm-var array<string, string> */ public $indexByMap = []; /** * Map from column names to class names that declare the field the column is mapped to. * * @ignore * @psalm-var array<string, class-string> */ public $declaringClasses = []; /** * This is necessary to hydrate derivate foreign keys correctly. * * @psalm-var array<string, array<string, bool>> */ public $isIdentifierColumn = []; /** * Maps column names in the result set to field names for each new object expression. * * @psalm-var array<string, array<string, mixed>> */ public $newObjectMappings = []; /** * Maps metadata parameter names to the metadata attribute. * * @psalm-var array<int|string, string> */ public $metadataParameterMapping = []; /** * Contains query parameter names to be resolved as discriminator values * * @psalm-var array<string, string> */ public $discriminatorParameters = []; /** * Adds an entity result to this ResultSetMapping. * * @param string $class The class name of the entity. * @param string $alias The alias for the class. The alias must be unique among all entity * results or joined entity results within this ResultSetMapping. * @param string|null $resultAlias The result alias with which the entity result should be * placed in the result structure. * @psalm-param class-string $class * * @return $this * * @todo Rename: addRootEntity */ public function addEntityResult($class, $alias, $resultAlias = null) { $this->aliasMap[$alias] = $class; $this->entityMappings[$alias] = $resultAlias; if ($resultAlias !== null) { $this->isMixed = true; } return $this; } /** * Sets a discriminator column for an entity result or joined entity result. * The discriminator column will be used to determine the concrete class name to * instantiate. * * @param string $alias The alias of the entity result or joined entity result the discriminator * column should be used for. * @param string $discrColumn The name of the discriminator column in the SQL result set. * * @return $this * * @todo Rename: addDiscriminatorColumn */ public function setDiscriminatorColumn($alias, $discrColumn) { $this->discriminatorColumns[$alias] = $discrColumn; $this->columnOwnerMap[$discrColumn] = $alias; return $this; } /** * Sets a field to use for indexing an entity result or joined entity result. * * @param string $alias The alias of an entity result or joined entity result. * @param string $fieldName The name of the field to use for indexing. * * @return $this */ public function addIndexBy($alias, $fieldName) { $found = false; foreach (array_merge($this->metaMappings, $this->fieldMappings) as $columnName => $columnFieldName) { if (! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) { continue; } $this->addIndexByColumn($alias, $columnName); $found = true; break; } /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals if ( ! $found) { $message = sprintf( 'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.', $alias, $fieldName ); throw new \LogicException($message); } */ return $this; } /** * Sets to index by a scalar result column name. * * @param string $resultColumnName * * @return $this */ public function addIndexByScalar($resultColumnName) { $this->indexByMap['scalars'] = $resultColumnName; return $this; } /** * Sets a column to use for indexing an entity or joined entity result by the given alias name. * * @param string $alias * @param string $resultColumnName * * @return $this */ public function addIndexByColumn($alias, $resultColumnName) { $this->indexByMap[$alias] = $resultColumnName; return $this; } /** * Checks whether an entity result or joined entity result with a given alias has * a field set for indexing. * * @param string $alias * * @return bool * * @todo Rename: isIndexed($alias) */ public function hasIndexBy($alias) { return isset($this->indexByMap[$alias]); } /** * Checks whether the column with the given name is mapped as a field result * as part of an entity result or joined entity result. * * @param string $columnName The name of the column in the SQL result set. * * @return bool * * @todo Rename: isField */ public function isFieldResult($columnName) { return isset($this->fieldMappings[$columnName]); } /** * Adds a field to the result that belongs to an entity or joined entity. * * @param string $alias The alias of the root entity or joined entity to which the field belongs. * @param string $columnName The name of the column in the SQL result set. * @param string $fieldName The name of the field on the declaring class. * @param string|null $declaringClass The name of the class that declares/owns the specified field. * When $alias refers to a superclass in a mapped hierarchy but * the field $fieldName is defined on a subclass, specify that here. * If not specified, the field is assumed to belong to the class * designated by $alias. * @psalm-param class-string|null $declaringClass * * @return $this * * @todo Rename: addField */ public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null) { // column name (in result set) => field name $this->fieldMappings[$columnName] = $fieldName; // column name => alias of owner $this->columnOwnerMap[$columnName] = $alias; // field name => class name of declaring class $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias]; if (! $this->isMixed && $this->scalarMappings) { $this->isMixed = true; } return $this; } /** * Adds a joined entity result. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param string $relation The association field that connects the parent entity result * with the joined entity result. * @psalm-param class-string $class * * @return $this * * @todo Rename: addJoinedEntity */ public function addJoinedEntityResult($class, $alias, $parentAlias, $relation) { $this->aliasMap[$alias] = $class; $this->parentAliasMap[$alias] = $parentAlias; $this->relationMap[$alias] = $relation; return $this; } /** * Adds a scalar result mapping. * * @param string $columnName The name of the column in the SQL result set. * @param string|int $alias The result alias with which the scalar result should be placed in the result structure. * @param string $type The column type * * @return $this * * @todo Rename: addScalar */ public function addScalarResult($columnName, $alias, $type = 'string') { $this->scalarMappings[$columnName] = $alias; $this->typeMappings[$columnName] = $type; if (! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; } return $this; } /** * Adds a scalar result mapping. * * @param string $columnName The name of the column in the SQL result set. * @param string $enumType The enum type * * @return $this */ public function addEnumResult($columnName, $enumType) { $this->enumMappings[$columnName] = $enumType; return $this; } /** * Adds a metadata parameter mappings. * * @param string|int $parameter The parameter name in the SQL result set. * @param string $attribute The metadata attribute. * * @return void */ public function addMetadataParameterMapping($parameter, $attribute) { $this->metadataParameterMapping[$parameter] = $attribute; } /** * Checks whether a column with a given name is mapped as a scalar result. * * @param string $columnName The name of the column in the SQL result set. * * @return bool * * @todo Rename: isScalar */ public function isScalarResult($columnName) { return isset($this->scalarMappings[$columnName]); } /** * Gets the name of the class of an entity result or joined entity result, * identified by the given unique alias. * * @param string $alias * * @return class-string */ public function getClassName($alias) { return $this->aliasMap[$alias]; } /** * Gets the field alias for a column that is mapped as a scalar value. * * @param string $columnName The name of the column in the SQL result set. * * @return string|int */ public function getScalarAlias($columnName) { return $this->scalarMappings[$columnName]; } /** * Gets the name of the class that owns a field mapping for the specified column. * * @param string $columnName * * @return class-string */ public function getDeclaringClass($columnName) { return $this->declaringClasses[$columnName]; } /** * @param string $alias * * @return string */ public function getRelation($alias) { return $this->relationMap[$alias]; } /** * @param string $alias * * @return bool */ public function isRelation($alias) { return isset($this->relationMap[$alias]); } /** * Gets the alias of the class that owns a field mapping for the specified column. * * @param string $columnName * * @return string */ public function getEntityAlias($columnName) { return $this->columnOwnerMap[$columnName]; } /** * Gets the parent alias of the given alias. * * @param string $alias * * @return string */ public function getParentAlias($alias) { return $this->parentAliasMap[$alias]; } /** * Checks whether the given alias has a parent alias. * * @param string $alias * * @return bool */ public function hasParentAlias($alias) { return isset($this->parentAliasMap[$alias]); } /** * Gets the field name for a column name. * * @param string $columnName * * @return string */ public function getFieldName($columnName) { return $this->fieldMappings[$columnName]; } /** @psalm-return array<string, class-string> */ public function getAliasMap() { return $this->aliasMap; } /** * Gets the number of different entities that appear in the mapped result. * * @return int * @psalm-return 0|positive-int */ public function getEntityResultCount() { return count($this->aliasMap); } /** * Checks whether this ResultSetMapping defines a mixed result. * * Mixed results can only occur in object and array (graph) hydration. In such a * case a mixed result means that scalar values are mixed with objects/array in * the result. * * @return bool */ public function isMixedResult() { return $this->isMixed; } /** * Adds a meta column (foreign key or discriminator column) to the result set. * * @param string $alias The result alias with which the meta result should be placed in the result structure. * @param string $columnName The name of the column in the SQL result set. * @param string $fieldName The name of the field on the declaring class. * @param bool $isIdentifierColumn * @param string|null $type The column type * * @return $this * * @todo Make all methods of this class require all parameters and not infer anything */ public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false, $type = null) { $this->metaMappings[$columnName] = $fieldName; $this->columnOwnerMap[$columnName] = $alias; if ($isIdentifierColumn) { $this->isIdentifierColumn[$alias][$columnName] = true; } if ($type) { $this->typeMappings[$columnName] = $type; } return $this; } } orm/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php 0000644 00000046263 15120025736 0016722 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Utility\PersisterHelper; use InvalidArgumentException; use LogicException; use function assert; use function explode; use function in_array; use function sprintf; use function str_contains; use function strtolower; /** * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields. */ class ResultSetMappingBuilder extends ResultSetMapping { use SQLResultCasing; /** * Picking this rename mode will register entity columns as is, * as they are in the database. This can cause clashes when multiple * entities are fetched that have columns with the same name. */ public const COLUMN_RENAMING_NONE = 1; /** * Picking custom renaming allows the user to define the renaming * of specific columns with a rename array that contains column names as * keys and result alias as values. */ public const COLUMN_RENAMING_CUSTOM = 2; /** * Incremental renaming uses a result set mapping internal counter to add a * number to each column result, leading to uniqueness. This only works if * you use {@see generateSelectClause()} to generate the SELECT clause for * you. */ public const COLUMN_RENAMING_INCREMENT = 3; /** @var int */ private $sqlCounter = 0; /** @var EntityManagerInterface */ private $em; /** * Default column renaming mode. * * @var int * @psalm-var self::COLUMN_RENAMING_* */ private $defaultRenameMode; /** * @param int $defaultRenameMode * @psalm-param self::COLUMN_RENAMING_* $defaultRenameMode */ public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE) { $this->em = $em; $this->defaultRenameMode = $defaultRenameMode; } /** * Adds a root entity and all of its fields to the result set. * * @param string $class The class name of the root entity. * @param string $alias The unique alias to use for the root entity. * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). * @psalm-param class-string $class * @psalm-param array<string, string> $renamedColumns * @psalm-param self::COLUMN_RENAMING_*|null $renameMode * * @return void */ public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = [], $renameMode = null) { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addEntityResult($class, $alias); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds a joined entity and all of its fields to the result set. * * @param string $class The class name of the joined entity. * @param string $alias The unique alias to use for the joined entity. * @param string $parentAlias The alias of the entity result that is the parent of this joined result. * @param string $relation The association field that connects the parent entity result * with the joined entity result. * @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName). * @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM). * @psalm-param class-string $class * @psalm-param array<string, string> $renamedColumns * @psalm-param self::COLUMN_RENAMING_*|null $renameMode * * @return void */ public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = [], $renameMode = null) { $renameMode = $renameMode ?: $this->defaultRenameMode; $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns); $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation); $this->addAllClassFields($class, $alias, $columnAliasMap); } /** * Adds all fields of the given class to the result set mapping (columns and meta fields). * * @param string $class * @param string $alias * @param string[] $columnAliasMap * @psalm-param array<string, string> $columnAliasMap * * @return void * * @throws InvalidArgumentException */ protected function addAllClassFields($class, $alias, $columnAliasMap = []) { $classMetadata = $this->em->getClassMetadata($class); $platform = $this->em->getConnection()->getDatabasePlatform(); if (! $this->isInheritanceSupported($classMetadata)) { throw new InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.'); } foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); if (isset($this->fieldMappings[$columnAlias])) { throw new InvalidArgumentException(sprintf( "The column '%s' conflicts with another column in the mapper.", $columnName )); } $this->addFieldResult($alias, $columnAlias, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadata::TO_ONE) { $targetClass = $this->em->getClassMetadata($associationMapping['targetEntity']); $isIdentifier = isset($associationMapping['id']) && $associationMapping['id'] === true; foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias = $this->getSQLResultCasing($platform, $columnAliasMap[$columnName]); $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); if (isset($this->metaMappings[$columnAlias])) { throw new InvalidArgumentException(sprintf( "The column '%s' conflicts with another column in the mapper.", $columnAlias )); } $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType); } } } } private function isInheritanceSupported(ClassMetadata $classMetadata): bool { if ( $classMetadata->isInheritanceTypeSingleTable() && in_array($classMetadata->name, $classMetadata->discriminatorMap, true) ) { return true; } return ! ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()); } /** * Gets column alias for a given column. * * @psalm-param array<string, string> $customRenameColumns * * @psalm-assert self::COLUMN_RENAMING_* $mode */ private function getColumnAlias(string $columnName, int $mode, array $customRenameColumns): string { switch ($mode) { case self::COLUMN_RENAMING_INCREMENT: return $columnName . $this->sqlCounter++; case self::COLUMN_RENAMING_CUSTOM: return $customRenameColumns[$columnName] ?? $columnName; case self::COLUMN_RENAMING_NONE: return $columnName; default: throw new InvalidArgumentException(sprintf( '%d is not a valid value for $mode', $mode )); } } /** * Retrieves a class columns and join columns aliases that are used in the SELECT clause. * * This depends on the renaming mode selected by the user. * * @psalm-param class-string $className * @psalm-param self::COLUMN_RENAMING_* $mode * @psalm-param array<string, string> $customRenameColumns * * @return string[] * @psalm-return array<array-key, string> */ private function getColumnAliasMap( string $className, int $mode, array $customRenameColumns ): array { if ($customRenameColumns) { // for BC with 2.2-2.3 API $mode = self::COLUMN_RENAMING_CUSTOM; } $columnAlias = []; $class = $this->em->getClassMetadata($className); foreach ($class->getColumnNames() as $columnName) { $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } foreach ($class->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadata::TO_ONE) { foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns); } } } return $columnAlias; } /** * Adds the mappings of the results of native SQL queries to the result set. * * @deprecated This method is deprecated and will be removed in Doctrine ORM 3.0. * * @param mixed[] $queryMapping * * @return ResultSetMappingBuilder */ public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) { if (isset($queryMapping['resultClass'])) { return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']); } return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']); } /** * Adds the class mapping of the results of native SQL queries to the result set. * * @deprecated This method is deprecated and will be removed in Doctrine ORM 3.0. * * @param string $resultClassName * * @return $this */ public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName) { $classMetadata = $this->em->getClassMetadata($resultClassName); assert($classMetadata->reflClass !== null); $shortName = $classMetadata->reflClass->getShortName(); $alias = strtolower($shortName[0]) . '0'; $this->addEntityResult($class->name, $alias); if ($classMetadata->discriminatorColumn) { $discrColumn = $classMetadata->discriminatorColumn; $this->setDiscriminatorColumn($alias, $discrColumn['name']); $this->addMetaResult($alias, $discrColumn['name'], $discrColumn['fieldName'], false, $discrColumn['type']); } foreach ($classMetadata->getColumnNames() as $key => $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } foreach ($classMetadata->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadata::TO_ONE) { $targetClass = $this->em->getClassMetadata($associationMapping['targetEntity']); foreach ($associationMapping['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName), $columnType); } } } return $this; } /** * Adds the result set mapping of the results of native SQL queries to the result set. * * @deprecated This method is deprecated and will be removed in Doctrine ORM 3.0. * * @param string $resultSetMappingName * * @return $this */ public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) { if ($class->reflClass === null) { throw new LogicException('Given class metadata has now class reflector.'); } $counter = 0; $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); $rootShortName = $class->reflClass->getShortName(); $rootAlias = strtolower($rootShortName[0]) . $counter; if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $entityMapping) { $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); assert($classMetadata->reflClass !== null); if ($class->reflClass->name === $classMetadata->reflClass->name) { $this->addEntityResult($classMetadata->name, $rootAlias); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias); } else { $shortName = $classMetadata->reflClass->getShortName(); $joinAlias = strtolower($shortName[0]) . ++$counter; $associations = $class->getAssociationsByTargetClass($classMetadata->name); $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias); foreach ($associations as $relation => $mapping) { $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation); } } } } if (isset($resultMapping['columns'])) { foreach ($resultMapping['columns'] as $entityMapping) { $type = isset($class->fieldNames[$entityMapping['name']]) ? PersisterHelper::getTypeOfColumn($entityMapping['name'], $class, $this->em) : 'string'; $this->addScalarResult($entityMapping['name'], $entityMapping['name'], $type); } } return $this; } /** * Adds the entity result mapping of the results of native SQL queries to the result set. * * @deprecated This method is deprecated and will be removed in Doctrine ORM 3.0. * * @param mixed[] $entityMapping * @param string $alias * * @return $this * * @throws MappingException * @throws InvalidArgumentException */ public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias) { if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { $discriminatorColumn = $entityMapping['discriminatorColumn']; $discriminatorType = $classMetadata->getDiscriminatorColumn()['type']; $this->setDiscriminatorColumn($alias, $discriminatorColumn); $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn, false, $discriminatorType); } if (isset($entityMapping['fields']) && ! empty($entityMapping['fields'])) { foreach ($entityMapping['fields'] as $field) { $fieldName = $field['name']; $relation = null; if (str_contains($fieldName, '.')) { [$relation, $fieldName] = explode('.', $fieldName); } if (isset($classMetadata->associationMappings[$relation])) { if ($relation) { $associationMapping = $classMetadata->associationMappings[$relation]; $joinAlias = $alias . $relation; $parentAlias = $alias; $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); $this->addFieldResult($joinAlias, $field['column'], $fieldName); } else { $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } else { if (! isset($classMetadata->fieldMappings[$fieldName])) { throw new InvalidArgumentException("Entity '" . $classMetadata->name . "' has no field '" . $fieldName . "'. "); } $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } } else { foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); } } return $this; } /** * Generates the Select clause from this ResultSetMappingBuilder. * * Works only for all the entity results. The select parts for scalar * expressions have to be written manually. * * @param string[] $tableAliases * @psalm-param array<string, string> $tableAliases * * @return string */ public function generateSelectClause($tableAliases = []) { $sql = ''; foreach ($this->columnOwnerMap as $columnName => $dqlAlias) { $tableAlias = $tableAliases[$dqlAlias] ?? $dqlAlias; if ($sql !== '') { $sql .= ', '; } if (isset($this->fieldMappings[$columnName])) { $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]); $fieldName = $this->fieldMappings[$columnName]; $classFieldMapping = $class->fieldMappings[$fieldName]; $columnSql = $tableAlias . '.' . $classFieldMapping['columnName']; if (isset($classFieldMapping['requireSQLConversion']) && $classFieldMapping['requireSQLConversion'] === true) { $type = Type::getType($classFieldMapping['type']); $columnSql = $type->convertToPHPValueSQL($columnSql, $this->em->getConnection()->getDatabasePlatform()); } $sql .= $columnSql; } elseif (isset($this->metaMappings[$columnName])) { $sql .= $tableAlias . '.' . $this->metaMappings[$columnName]; } elseif (isset($this->discriminatorColumns[$dqlAlias])) { $sql .= $tableAlias . '.' . $this->discriminatorColumns[$dqlAlias]; } $sql .= ' AS ' . $columnName; } return $sql; } /** @return string */ public function __toString() { return $this->generateSelectClause([]); } } orm/lib/Doctrine/ORM/Query/SqlWalker.php 0000644 00000276332 15120025736 0014054 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use BadMethodCallException; use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\Query; use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; use Doctrine\ORM\Utility\PersisterHelper; use InvalidArgumentException; use LogicException; use function array_diff; use function array_filter; use function array_keys; use function array_map; use function array_merge; use function assert; use function count; use function implode; use function in_array; use function is_array; use function is_float; use function is_numeric; use function is_string; use function preg_match; use function reset; use function sprintf; use function strtolower; use function strtoupper; use function trim; /** * The SqlWalker walks over a DQL AST and constructs the corresponding SQL. * * @psalm-import-type QueryComponent from Parser * @psalm-consistent-constructor */ class SqlWalker implements TreeWalker { public const HINT_DISTINCT = 'doctrine.distinct'; /** * Used to mark a query as containing a PARTIAL expression, which needs to be known by SLC. */ public const HINT_PARTIAL = 'doctrine.partial'; /** @var ResultSetMapping */ private $rsm; /** * Counter for generating unique column aliases. * * @var int */ private $aliasCounter = 0; /** * Counter for generating unique table aliases. * * @var int */ private $tableAliasCounter = 0; /** * Counter for generating unique scalar result. * * @var int */ private $scalarResultCounter = 1; /** * Counter for generating unique parameter indexes. * * @var int */ private $sqlParamIndex = 0; /** * Counter for generating indexes. * * @var int */ private $newObjectCounter = 0; /** @var ParserResult */ private $parserResult; /** @var EntityManagerInterface */ private $em; /** @var Connection */ private $conn; /** @var Query */ private $query; /** @var mixed[] */ private $tableAliasMap = []; /** * Map from result variable names to their SQL column alias names. * * @psalm-var array<string|int, string|list<string>> */ private $scalarResultAliasMap = []; /** * Map from Table-Alias + Column-Name to OrderBy-Direction. * * @var array<string, string> */ private $orderedColumnsMap = []; /** * Map from DQL-Alias + Field-Name to SQL Column Alias. * * @var array<string, array<string, string>> */ private $scalarFields = []; /** * Map of all components/classes that appear in the DQL query. * * @psalm-var array<string, QueryComponent> */ private $queryComponents; /** * A list of classes that appear in non-scalar SelectExpressions. * * @psalm-var array<string, array{class: ClassMetadata, dqlAlias: string, resultAlias: string|null}> */ private $selectedClasses = []; /** * The DQL alias of the root class of the currently traversed query. * * @psalm-var list<string> */ private $rootAliases = []; /** * Flag that indicates whether to generate SQL table aliases in the SQL. * These should only be generated for SELECT queries, not for UPDATE/DELETE. * * @var bool */ private $useSqlTableAliases = true; /** * The database platform abstraction. * * @var AbstractPlatform */ private $platform; /** * The quote strategy. * * @var QuoteStrategy */ private $quoteStrategy; /** * @param Query $query The parsed Query. * @param ParserResult $parserResult The result of the parsing process. * @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table). */ public function __construct($query, $parserResult, array $queryComponents) { $this->query = $query; $this->parserResult = $parserResult; $this->queryComponents = $queryComponents; $this->rsm = $parserResult->getResultSetMapping(); $this->em = $query->getEntityManager(); $this->conn = $this->em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); } /** * Gets the Query instance used by the walker. * * @return Query */ public function getQuery() { return $this->query; } /** * Gets the Connection used by the walker. * * @return Connection */ public function getConnection() { return $this->conn; } /** * Gets the EntityManager used by the walker. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->em; } /** * Gets the information about a single query component. * * @param string $dqlAlias The DQL alias. * * @return mixed[] * @psalm-return QueryComponent */ public function getQueryComponent($dqlAlias) { return $this->queryComponents[$dqlAlias]; } public function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata { if (! isset($this->queryComponents[$dqlAlias]['metadata'])) { throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); } return $this->queryComponents[$dqlAlias]['metadata']; } /** * Returns internal queryComponents array. * * @return array<string, QueryComponent> */ public function getQueryComponents() { return $this->queryComponents; } /** * Sets or overrides a query component for a given dql alias. * * @param string $dqlAlias The DQL alias. * @psalm-param QueryComponent $queryComponent * * @return void * * @not-deprecated */ public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token']; if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->queryComponents[$dqlAlias] = $queryComponent; } /** * Gets an executor that can be used to execute the result of this walker. * * @param AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST * * @return Exec\AbstractSqlExecutor * * @not-deprecated */ public function getExecutor($AST) { switch (true) { case $AST instanceof AST\DeleteStatement: $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName); return $primaryClass->isInheritanceTypeJoined() ? new Exec\MultiTableDeleteExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); case $AST instanceof AST\UpdateStatement: $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName); return $primaryClass->isInheritanceTypeJoined() ? new Exec\MultiTableUpdateExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this); default: return new Exec\SingleSelectExecutor($AST, $this); } } /** * Generates a unique, short SQL table alias. * * @param string $tableName Table name * @param string $dqlAlias The DQL alias. * * @return string Generated table alias. */ public function getSQLTableAlias($tableName, $dqlAlias = '') { $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : ''; if (! isset($this->tableAliasMap[$tableName])) { $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't') . $this->tableAliasCounter++ . '_'; } return $this->tableAliasMap[$tableName]; } /** * Forces the SqlWalker to use a specific alias for a table name, rather than * generating an alias on its own. * * @param string $tableName * @param string $alias * @param string $dqlAlias * * @return string */ public function setSQLTableAlias($tableName, $alias, $dqlAlias = '') { $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : ''; $this->tableAliasMap[$tableName] = $alias; return $alias; } /** * Gets an SQL column alias for a column name. * * @param string $columnName * * @return string */ public function getSQLColumnAlias($columnName) { return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform); } /** * Generates the SQL JOINs that are necessary for Class Table Inheritance * for the given class. * * @param ClassMetadata $class The class for which to generate the joins. * @param string $dqlAlias The DQL alias of the class. * * @return string The SQL. */ private function generateClassTableInheritanceJoins( ClassMetadata $class, string $dqlAlias ): string { $sql = ''; $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); // INNER JOIN parent class tables foreach ($class->parentClasses as $parentClassName) { $parentClass = $this->em->getClassMetadata($parentClassName); $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias); // If this is a joined association we must use left joins to preserve the correct result. $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = []; foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } // Add filters on the root class $sqlParts[] = $this->generateFilterConditionSQL($parentClass, $tableAlias); $sql .= implode(' AND ', array_filter($sqlParts)); } // Ignore subclassing inclusion if partial objects is disallowed if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { return $sql; } // LEFT JOIN child class tables foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON '; $sqlParts = []; foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) { $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName; } $sql .= implode(' AND ', $sqlParts); } return $sql; } private function generateOrderedCollectionOrderByItems(): string { $orderedColumns = []; foreach ($this->selectedClasses as $selectedClass) { $dqlAlias = $selectedClass['dqlAlias']; $qComp = $this->queryComponents[$dqlAlias]; if (! isset($qComp['relation']['orderBy'])) { continue; } assert(isset($qComp['metadata'])); $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) { $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform); $tableName = $qComp['metadata']->isInheritanceTypeJoined() ? $persister->getOwningTable($fieldName) : $qComp['metadata']->getTableName(); $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName; // OrderByClause should replace an ordered relation. see - DDC-2475 if (isset($this->orderedColumnsMap[$orderedColumn])) { continue; } $this->orderedColumnsMap[$orderedColumn] = $orientation; $orderedColumns[] = $orderedColumn . ' ' . $orientation; } } return implode(', ', $orderedColumns); } /** * Generates a discriminator column SQL condition for the class with the given DQL alias. * * @psalm-param list<string> $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. */ private function generateDiscriminatorColumnConditionSQL(array $dqlAliases): string { $sqlParts = []; foreach ($dqlAliases as $dqlAlias) { $class = $this->getMetadataForDqlAlias($dqlAlias); if (! $class->isInheritanceTypeSingleTable()) { continue; } $conn = $this->em->getConnection(); $values = []; if ($class->discriminatorValue !== null) { // discriminators can be 0 $values[] = $conn->quote($class->discriminatorValue); } foreach ($class->subClasses as $subclassName) { $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue); } $sqlTableAlias = $this->useSqlTableAliases ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : ''; $sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()['name'] . ' IN (' . implode(', ', $values) . ')'; } $sql = implode(' AND ', $sqlParts); return count($sqlParts) > 1 ? '(' . $sql . ')' : $sql; } /** * Generates the filter SQL for a given entity and table alias. * * @param ClassMetadata $targetEntity Metadata of the target entity. * @param string $targetTableAlias The table alias of the joined/selected table. * * @return string The SQL query part to add to a query. */ private function generateFilterConditionSQL( ClassMetadata $targetEntity, string $targetTableAlias ): string { if (! $this->em->hasFilters()) { return ''; } switch ($targetEntity->inheritanceType) { case ClassMetadata::INHERITANCE_TYPE_NONE: break; case ClassMetadata::INHERITANCE_TYPE_JOINED: // The classes in the inheritance will be added to the query one by one, // but only the root node is getting filtered if ($targetEntity->name !== $targetEntity->rootEntityName) { return ''; } break; case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE: // With STI the table will only be queried once, make sure that the filters // are added to the root entity $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName); break; default: //@todo: throw exception? return ''; } $filterClauses = []; foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias); if ($filterExpr !== '') { $filterClauses[] = '(' . $filterExpr . ')'; } } return implode(' AND ', $filterClauses); } /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * * @return string */ public function walkSelectStatement(AST\SelectStatement $AST) { $limit = $this->query->getMaxResults(); $offset = $this->query->getFirstResult(); $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE; $sql = $this->walkSelectClause($AST->selectClause) . $this->walkFromClause($AST->fromClause) . $this->walkWhereClause($AST->whereClause); if ($AST->groupByClause) { $sql .= $this->walkGroupByClause($AST->groupByClause); } if ($AST->havingClause) { $sql .= $this->walkHavingClause($AST->havingClause); } if ($AST->orderByClause) { $sql .= $this->walkOrderByClause($AST->orderByClause); } $orderBySql = $this->generateOrderedCollectionOrderByItems(); if (! $AST->orderByClause && $orderBySql) { $sql .= ' ORDER BY ' . $orderBySql; } $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset); if ($lockMode === LockMode::NONE) { return $sql; } if ($lockMode === LockMode::PESSIMISTIC_READ) { return $sql . ' ' . $this->platform->getReadLockSQL(); } if ($lockMode === LockMode::PESSIMISTIC_WRITE) { return $sql . ' ' . $this->platform->getWriteLockSQL(); } if ($lockMode !== LockMode::OPTIMISTIC) { throw QueryException::invalidLockMode(); } foreach ($this->selectedClasses as $selectedClass) { if (! $selectedClass['class']->isVersioned) { throw OptimisticLockException::lockFailed($selectedClass['class']->name); } } return $sql; } /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. * * @return string */ public function walkUpdateStatement(AST\UpdateStatement $AST) { $this->useSqlTableAliases = false; $this->rsm->isSelect = false; return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); } /** * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. * * @return string */ public function walkDeleteStatement(AST\DeleteStatement $AST) { $this->useSqlTableAliases = false; $this->rsm->isSelect = false; return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); } /** * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL. * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers. * * @param string $identVariable * * @return string * * @not-deprecated */ public function walkEntityIdentificationVariable($identVariable) { $class = $this->getMetadataForDqlAlias($identVariable); $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable); $sqlParts = []; foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) { $sqlParts[] = $tableAlias . '.' . $columnName; } return implode(', ', $sqlParts); } /** * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. * * @param string $identificationVariable * @param string $fieldName * * @return string The SQL. * * @not-deprecated */ public function walkIdentificationVariable($identificationVariable, $fieldName = null) { $class = $this->getMetadataForDqlAlias($identificationVariable); if ( $fieldName !== null && $class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited']) ) { $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); } return $this->getSQLTableAlias($class->getTableName(), $identificationVariable); } /** * Walks down a PathExpression AST node, thereby generating the appropriate SQL. * * @param AST\PathExpression $pathExpr * * @return string * * @not-deprecated */ public function walkPathExpression($pathExpr) { $sql = ''; assert($pathExpr->field !== null); switch ($pathExpr->type) { case AST\PathExpression::TYPE_STATE_FIELD: $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->getMetadataForDqlAlias($dqlAlias); if ($this->useSqlTableAliases) { $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; } $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); break; case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // 1- the owning side: // Just use the foreign key, i.e. u.group_id $fieldName = $pathExpr->field; $dqlAlias = $pathExpr->identificationVariable; $class = $this->getMetadataForDqlAlias($dqlAlias); if (isset($class->associationMappings[$fieldName]['inherited'])) { $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); } $assoc = $class->associationMappings[$fieldName]; if (! $assoc['isOwningSide']) { throw QueryException::associationPathInverseSideNotSupported($pathExpr); } // COMPOSITE KEYS NOT (YET?) SUPPORTED if (count($assoc['sourceToTargetKeyColumns']) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); } if ($this->useSqlTableAliases) { $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'; } $sql .= reset($assoc['targetToSourceKeyColumns']); break; default: throw QueryException::invalidPathExpression($pathExpr); } return $sql; } /** * Walks down a SelectClause AST node, thereby generating the appropriate SQL. * * @param AST\SelectClause $selectClause * * @return string * * @not-deprecated */ public function walkSelectClause($selectClause) { $sql = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : ''); $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions)); if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) { $this->query->setHint(self::HINT_DISTINCT, true); } $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $this->query->getHydrationMode() === Query::HYDRATE_OBJECT || $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS); foreach ($this->selectedClasses as $selectedClass) { $class = $selectedClass['class']; $dqlAlias = $selectedClass['dqlAlias']; $resultAlias = $selectedClass['resultAlias']; // Register as entity or joined entity result if (! isset($this->queryComponents[$dqlAlias]['relation'])) { $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias); } else { assert(isset($this->queryComponents[$dqlAlias]['parent'])); $this->rsm->addJoinedEntityResult( $class->name, $dqlAlias, $this->queryComponents[$dqlAlias]['parent'], $this->queryComponents[$dqlAlias]['relation']['fieldName'] ); } if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { // Add discriminator columns to SQL $rootClass = $this->em->getClassMetadata($class->rootEntityName); $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias); $discrColumn = $rootClass->getDiscriminatorColumn(); $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias; $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']); if (! empty($discrColumn['enumType'])) { $this->rsm->addEnumResult($columnAlias, $discrColumn['enumType']); } } // Add foreign key columns to SQL, if necessary if (! $addMetaColumns && ! $class->containsForeignIdentifier) { continue; } // Add foreign key columns of class and also parent classes foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) || ( ! $addMetaColumns && ! isset($assoc['id'])) ) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $isIdentifier = (isset($assoc['id']) && $assoc['id'] === true); $owningClass = isset($assoc['inherited']) ? $this->em->getClassMetadata($assoc['inherited']) : $class; $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); foreach ($assoc['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias = $this->getSQLColumnAlias($columnName); $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType); } } // Add foreign key columns to SQL, if necessary if (! $addMetaColumns) { continue; } // Add foreign key columns of subclasses foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->associationMappings as $assoc) { // Skip if association is inherited if (isset($assoc['inherited'])) { continue; } if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($assoc['joinColumns'] as $joinColumn) { $columnName = $joinColumn['name']; $columnAlias = $this->getSQLColumnAlias($columnName); $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform); $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias; $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType); } } } } } return $sql . implode(', ', $sqlSelectExpressions); } /** * Walks down a FromClause AST node, thereby generating the appropriate SQL. * * @param AST\FromClause $fromClause * * @return string * * @not-deprecated */ public function walkFromClause($fromClause) { $identificationVarDecls = $fromClause->identificationVariableDeclarations; $sqlParts = []; foreach ($identificationVarDecls as $identificationVariableDecl) { $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl); } return ' FROM ' . implode(', ', $sqlParts); } /** * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL. * * @param AST\IdentificationVariableDeclaration $identificationVariableDecl * * @return string * * @not-deprecated */ public function walkIdentificationVariableDeclaration($identificationVariableDecl) { $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration); if ($identificationVariableDecl->indexBy) { $this->walkIndexBy($identificationVariableDecl->indexBy); } foreach ($identificationVariableDecl->joins as $join) { $sql .= $this->walkJoin($join); } return $sql; } /** * Walks down a IndexBy AST node. * * @param AST\IndexBy $indexBy * * @return void * * @not-deprecated */ public function walkIndexBy($indexBy) { $pathExpression = $indexBy->singleValuedPathExpression; $alias = $pathExpression->identificationVariable; assert($pathExpression->field !== null); switch ($pathExpression->type) { case AST\PathExpression::TYPE_STATE_FIELD: $field = $pathExpression->field; break; case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: // Just use the foreign key, i.e. u.group_id $fieldName = $pathExpression->field; $class = $this->getMetadataForDqlAlias($alias); if (isset($class->associationMappings[$fieldName]['inherited'])) { $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); } $association = $class->associationMappings[$fieldName]; if (! $association['isOwningSide']) { throw QueryException::associationPathInverseSideNotSupported($pathExpression); } if (count($association['sourceToTargetKeyColumns']) > 1) { throw QueryException::associationPathCompositeKeyNotSupported(); } $field = reset($association['targetToSourceKeyColumns']); break; default: throw QueryException::invalidPathExpression($pathExpression); } if (isset($this->scalarFields[$alias][$field])) { $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]); return; } $this->rsm->addIndexBy($alias, $field); } /** * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL. * * @param AST\RangeVariableDeclaration $rangeVariableDeclaration * * @return string * * @not-deprecated */ public function walkRangeVariableDeclaration($rangeVariableDeclaration) { return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false); } /** * Generate appropriate SQL for RangeVariableDeclaration AST node */ private function generateRangeVariableDeclarationSQL( AST\RangeVariableDeclaration $rangeVariableDeclaration, bool $buildNestedJoins ): string { $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName); $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable; if ($rangeVariableDeclaration->isRoot) { $this->rootAliases[] = $dqlAlias; } $sql = $this->platform->appendLockHint( $this->quoteStrategy->getTableName($class, $this->platform) . ' ' . $this->getSQLTableAlias($class->getTableName(), $dqlAlias), $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE ); if (! $class->isInheritanceTypeJoined()) { return $sql; } $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias); if (! $buildNestedJoins) { return $sql . $classTableInheritanceJoins; } return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')'; } /** * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL. * * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration * @param int $joinType * @param AST\ConditionalExpression $condExpr * @psalm-param AST\Join::JOIN_TYPE_* $joinType * * @return string * * @throws QueryException * * @not-deprecated */ public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null) { $sql = ''; $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression; $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable; $indexBy = $joinAssociationDeclaration->indexBy; $relation = $this->queryComponents[$joinedDqlAlias]['relation'] ?? null; assert($relation !== null); $targetClass = $this->em->getClassMetadata($relation['targetEntity']); $sourceClass = $this->em->getClassMetadata($relation['sourceEntity']); $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias); $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable); // Ensure we got the owning side, since it has all mapping info $assoc = ! $relation['isOwningSide'] ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) { if ($relation['type'] === ClassMetadata::ONE_TO_MANY || $relation['type'] === ClassMetadata::MANY_TO_MANY) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } } $targetTableJoin = null; // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot // be the owning side and previously we ensured that $assoc is always the owning side of the associations. // The owning side is necessary at this point because only it contains the JoinColumn information. switch (true) { case $assoc['type'] & ClassMetadata::TO_ONE: $conditions = []; foreach ($assoc['joinColumns'] as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); if ($relation['isOwningSide']) { $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; continue; } $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $targetTableJoin = [ 'table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions), ]; break; case $assoc['type'] === ClassMetadata::MANY_TO_MANY: // Join relation table $joinTable = $assoc['joinTable']; $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias); $joinTableName = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform); $conditions = []; $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['joinColumns'] : $assoc['joinTable']['inverseJoinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions); // Join target table $sql .= $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN '; $conditions = []; $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['inverseJoinColumns'] : $assoc['joinTable']['joinColumns']; foreach ($relationColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform); $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform); $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn; } // Apply remaining inheritance restrictions $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } $targetTableJoin = [ 'table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions), ]; break; default: throw new BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY'); } // Handle WITH clause $withCondition = $condExpr === null ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')'); if ($targetClass->isInheritanceTypeJoined()) { $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); // If we have WITH condition, we need to build nested joins for target class table and cti joins if ($withCondition && $ctiJoins) { $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition']; } else { $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins; } } else { $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition']; } if ($withCondition) { $sql .= ' AND ' . $withCondition; } // Apply the indexes if ($indexBy) { // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. $this->walkIndexBy($indexBy); } elseif (isset($relation['indexBy'])) { $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']); } return $sql; } /** * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. * * @param AST\Functions\FunctionNode $function * * @return string * * @not-deprecated */ public function walkFunction($function) { return $function->getSql($this); } /** * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. * * @param AST\OrderByClause $orderByClause * * @return string * * @not-deprecated */ public function walkOrderByClause($orderByClause) { $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems); $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems(); if ($collectionOrderByItems !== '') { $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems); } return ' ORDER BY ' . implode(', ', $orderByItems); } /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @param AST\OrderByItem $orderByItem * * @return string * * @not-deprecated */ public function walkOrderByItem($orderByItem) { $type = strtoupper($orderByItem->type); $expr = $orderByItem->expression; $sql = $expr instanceof AST\Node ? $expr->dispatch($this) : $this->walkResultVariable($this->queryComponents[$expr]['token']->value); $this->orderedColumnsMap[$sql] = $type; if ($expr instanceof AST\Subselect) { return '(' . $sql . ') ' . $type; } return $sql . ' ' . $type; } /** * Walks down a HavingClause AST node, thereby generating the appropriate SQL. * * @param AST\HavingClause $havingClause * * @return string The SQL. * * @not-deprecated */ public function walkHavingClause($havingClause) { return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); } /** * Walks down a Join AST node and creates the corresponding SQL. * * @param AST\Join $join * * @return string * * @not-deprecated */ public function walkJoin($join) { $joinType = $join->joinType; $joinDeclaration = $join->joinAssociationDeclaration; $sql = $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN '; switch (true) { case $joinDeclaration instanceof AST\RangeVariableDeclaration: $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName); $dqlAlias = $joinDeclaration->aliasIdentificationVariable; $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $conditions = []; if ($join->conditionalExpression) { $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')'; } $isUnconditionalJoin = $conditions === []; $condExprConjunction = $class->isInheritanceTypeJoined() && $joinType !== AST\Join::JOIN_TYPE_LEFT && $joinType !== AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin ? ' AND ' : ' ON '; $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin); // Apply remaining inheritance restrictions $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]); if ($discrSql) { $conditions[] = $discrSql; } // Apply the filters $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias); if ($filterExpr) { $conditions[] = $filterExpr; } if ($conditions) { $sql .= $condExprConjunction . implode(' AND ', $conditions); } break; case $joinDeclaration instanceof AST\JoinAssociationDeclaration: $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression); break; } return $sql; } /** * Walks down a CoalesceExpression AST node and generates the corresponding SQL. * * @param AST\CoalesceExpression $coalesceExpression * * @return string The SQL. * * @not-deprecated */ public function walkCoalesceExpression($coalesceExpression) { $sql = 'COALESCE('; $scalarExpressions = []; foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); } return $sql . implode(', ', $scalarExpressions) . ')'; } /** * Walks down a NullIfExpression AST node and generates the corresponding SQL. * * @param AST\NullIfExpression $nullIfExpression * * @return string The SQL. * * @not-deprecated */ public function walkNullIfExpression($nullIfExpression) { $firstExpression = is_string($nullIfExpression->firstExpression) ? $this->conn->quote($nullIfExpression->firstExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); $secondExpression = is_string($nullIfExpression->secondExpression) ? $this->conn->quote($nullIfExpression->secondExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; } /** * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL. * * @return string The SQL. * * @not-deprecated */ public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression) { $sql = 'CASE'; foreach ($generalCaseExpression->whenClauses as $whenClause) { $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression); } $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END'; return $sql; } /** * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL. * * @param AST\SimpleCaseExpression $simpleCaseExpression * * @return string The SQL. * * @not-deprecated */ public function walkSimpleCaseExpression($simpleCaseExpression) { $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand); foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) { $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression); $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression); } $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END'; return $sql; } /** * Walks down a SelectExpression AST node and generates the corresponding SQL. * * @param AST\SelectExpression $selectExpression * * @return string * * @not-deprecated */ public function walkSelectExpression($selectExpression) { $sql = ''; $expr = $selectExpression->expression; $hidden = $selectExpression->hiddenAliasResultVariable; switch (true) { case $expr instanceof AST\PathExpression: if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) { throw QueryException::invalidPathExpression($expr); } assert($expr->field !== null); $fieldName = $expr->field; $dqlAlias = $expr->identificationVariable; $class = $this->getMetadataForDqlAlias($dqlAlias); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName; $tableName = $class->isInheritanceTypeJoined() ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $fieldMapping = $class->fieldMappings[$fieldName]; $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']); $col = $sqlTableAlias . '.' . $columnName; if (isset($fieldMapping['requireSQLConversion'])) { $type = Type::getType($fieldMapping['type']); $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()); } $sql .= $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if (! $hidden) { $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']); $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias; if (! empty($fieldMapping['enumType'])) { $this->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']); } } break; case $expr instanceof AST\AggregateExpression: case $expr instanceof AST\Functions\FunctionNode: case $expr instanceof AST\SimpleArithmeticExpression: case $expr instanceof AST\ArithmeticTerm: case $expr instanceof AST\ArithmeticFactor: case $expr instanceof AST\ParenthesisExpression: case $expr instanceof AST\Literal: case $expr instanceof AST\NullIfExpression: case $expr instanceof AST\CoalesceExpression: case $expr instanceof AST\GeneralCaseExpression: case $expr instanceof AST\SimpleCaseExpression: $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if ($hidden) { break; } if (! $expr instanceof Query\AST\TypedExpression) { // Conceptually we could resolve field type here by traverse through AST to retrieve field type, // but this is not a feasible solution; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); break; } $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getTypeRegistry()->lookupName($expr->getReturnType())); break; case $expr instanceof AST\Subselect: $columnAlias = $this->getSQLColumnAlias('sclr'); $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias] = $columnAlias; if (! $hidden) { // We cannot resolve field type here; assume 'string'. $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; case $expr instanceof AST\NewObjectExpression: $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable); break; default: // IdentificationVariable or PartialObjectExpression if ($expr instanceof AST\PartialObjectExpression) { $this->query->setHint(self::HINT_PARTIAL, true); $dqlAlias = $expr->identificationVariable; $partialFieldSet = $expr->partialFieldSet; } else { $dqlAlias = $expr; $partialFieldSet = []; } $class = $this->getMetadataForDqlAlias($dqlAlias); $resultAlias = $selectExpression->fieldIdentificationVariable ?: null; if (! isset($this->selectedClasses[$dqlAlias])) { $this->selectedClasses[$dqlAlias] = [ 'class' => $class, 'dqlAlias' => $dqlAlias, 'resultAlias' => $resultAlias, ]; } $sqlParts = []; // Select all fields from the queried class foreach ($class->fieldMappings as $fieldName => $mapping) { if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true)) { continue; } $tableName = isset($mapping['inherited']) ? $this->em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName(); $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($mapping['requireSQLConversion'])) { $type = Type::getType($mapping['type']); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); if (! empty($mapping['enumType'])) { $this->rsm->addEnumResult($columnAlias, $mapping['enumType']); } } // Add any additional fields of subclasses (excluding inherited fields) // 1) on Single Table Inheritance: always, since its marginal overhead // 2) on Class Table Inheritance only if partial objects are disallowed, // since it requires outer joining subtables. if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias); foreach ($subClass->fieldMappings as $fieldName => $mapping) { if (isset($mapping['inherited']) || ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true))) { continue; } $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform); $col = $sqlTableAlias . '.' . $quotedColumnName; if (isset($mapping['requireSQLConversion'])) { $type = Type::getType($mapping['type']); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlParts[] = $col . ' AS ' . $columnAlias; $this->scalarResultAliasMap[$resultAlias][] = $columnAlias; $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } } $sql .= implode(', ', $sqlParts); } return $sql; } /** * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. * * @param AST\QuantifiedExpression $qExpr * * @return string * * @not-deprecated */ public function walkQuantifiedExpression($qExpr) { return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')'; } /** * Walks down a Subselect AST node, thereby generating the appropriate SQL. * * @param AST\Subselect $subselect * * @return string * * @not-deprecated */ public function walkSubselect($subselect) { $useAliasesBefore = $this->useSqlTableAliases; $rootAliasesBefore = $this->rootAliases; $this->rootAliases = []; // reset the rootAliases for the subselect $this->useSqlTableAliases = true; $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); $sql .= $this->walkWhereClause($subselect->whereClause); $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; $this->rootAliases = $rootAliasesBefore; // put the main aliases back $this->useSqlTableAliases = $useAliasesBefore; return $sql; } /** * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. * * @param AST\SubselectFromClause $subselectFromClause * * @return string * * @not-deprecated */ public function walkSubselectFromClause($subselectFromClause) { $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; $sqlParts = []; foreach ($identificationVarDecls as $subselectIdVarDecl) { $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl); } return ' FROM ' . implode(', ', $sqlParts); } /** * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. * * @param AST\SimpleSelectClause $simpleSelectClause * * @return string * * @not-deprecated */ public function walkSimpleSelectClause($simpleSelectClause) { return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); } /** @return string */ public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression) { return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this)); } /** * @param AST\NewObjectExpression $newObjectExpression * @param string|null $newObjectResultAlias * * @return string The SQL. */ public function walkNewObject($newObjectExpression, $newObjectResultAlias = null) { $sqlSelectExpressions = []; $objIndex = $newObjectResultAlias ?: $this->newObjectCounter++; foreach ($newObjectExpression->args as $argIndex => $e) { $resultAlias = $this->scalarResultCounter++; $columnAlias = $this->getSQLColumnAlias('sclr'); $fieldType = 'string'; switch (true) { case $e instanceof AST\NewObjectExpression: $sqlSelectExpressions[] = $e->dispatch($this); break; case $e instanceof AST\Subselect: $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias; break; case $e instanceof AST\PathExpression: assert($e->field !== null); $dqlAlias = $e->identificationVariable; $class = $this->getMetadataForDqlAlias($dqlAlias); $fieldName = $e->field; $fieldMapping = $class->fieldMappings[$fieldName]; $fieldType = $fieldMapping['type']; $col = trim($e->dispatch($this)); if (isset($fieldMapping['requireSQLConversion'])) { $type = Type::getType($fieldType); $col = $type->convertToPHPValueSQL($col, $this->platform); } $sqlSelectExpressions[] = $col . ' AS ' . $columnAlias; if (! empty($fieldMapping['enumType'])) { $this->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']); } break; case $e instanceof AST\Literal: switch ($e->type) { case AST\Literal::BOOLEAN: $fieldType = 'boolean'; break; case AST\Literal::NUMERIC: $fieldType = is_float($e->value) ? 'float' : 'integer'; break; } $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; break; default: $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias; break; } $this->scalarResultAliasMap[$resultAlias] = $columnAlias; $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); $this->rsm->newObjectMappings[$columnAlias] = [ 'className' => $newObjectExpression->className, 'objIndex' => $objIndex, 'argIndex' => $argIndex, ]; } return implode(', ', $sqlSelectExpressions); } /** * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. * * @param AST\SimpleSelectExpression $simpleSelectExpression * * @return string * * @not-deprecated */ public function walkSimpleSelectExpression($simpleSelectExpression) { $expr = $simpleSelectExpression->expression; $sql = ' '; switch (true) { case $expr instanceof AST\PathExpression: $sql .= $this->walkPathExpression($expr); break; case $expr instanceof AST\Subselect: $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = 'sclr' . $this->aliasCounter++; $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; break; case $expr instanceof AST\Functions\FunctionNode: case $expr instanceof AST\SimpleArithmeticExpression: case $expr instanceof AST\ArithmeticTerm: case $expr instanceof AST\ArithmeticFactor: case $expr instanceof AST\Literal: case $expr instanceof AST\NullIfExpression: case $expr instanceof AST\CoalesceExpression: case $expr instanceof AST\GeneralCaseExpression: case $expr instanceof AST\SimpleCaseExpression: $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++; $columnAlias = $this->getSQLColumnAlias('sclr'); $this->scalarResultAliasMap[$alias] = $columnAlias; $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias; break; case $expr instanceof AST\ParenthesisExpression: $sql .= $this->walkParenthesisExpression($expr); break; default: // IdentificationVariable $sql .= $this->walkEntityIdentificationVariable($expr); break; } return $sql; } /** * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. * * @param AST\AggregateExpression $aggExpression * * @return string * * @not-deprecated */ public function walkAggregateExpression($aggExpression) { return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; } /** * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. * * @param AST\GroupByClause $groupByClause * * @return string * * @not-deprecated */ public function walkGroupByClause($groupByClause) { $sqlParts = []; foreach ($groupByClause->groupByItems as $groupByItem) { $sqlParts[] = $this->walkGroupByItem($groupByItem); } return ' GROUP BY ' . implode(', ', $sqlParts); } /** * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. * * @param AST\PathExpression|string $groupByItem * * @return string * * @not-deprecated */ public function walkGroupByItem($groupByItem) { // StateFieldPathExpression if (! is_string($groupByItem)) { return $this->walkPathExpression($groupByItem); } // ResultVariable if (isset($this->queryComponents[$groupByItem]['resultVariable'])) { $resultVariable = $this->queryComponents[$groupByItem]['resultVariable']; if ($resultVariable instanceof AST\PathExpression) { return $this->walkPathExpression($resultVariable); } if ($resultVariable instanceof AST\Node && isset($resultVariable->pathExpression)) { return $this->walkPathExpression($resultVariable->pathExpression); } return $this->walkResultVariable($groupByItem); } // IdentificationVariable $sqlParts = []; foreach ($this->getMetadataForDqlAlias($groupByItem)->fieldNames as $field) { $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); $item->type = AST\PathExpression::TYPE_STATE_FIELD; $sqlParts[] = $this->walkPathExpression($item); } foreach ($this->getMetadataForDqlAlias($groupByItem)->associationMappings as $mapping) { if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadata::TO_ONE) { $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; $sqlParts[] = $this->walkPathExpression($item); } } return implode(', ', $sqlParts); } /** * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. * * @return string * * @not-deprecated */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable); $this->rootAliases[] = $deleteClause->aliasIdentificationVariable; return $sql; } /** * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. * * @param AST\UpdateClause $updateClause * * @return string * * @not-deprecated */ public function walkUpdateClause($updateClause) { $class = $this->em->getClassMetadata($updateClause->abstractSchemaName); $tableName = $class->getTableName(); $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform); $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable); $this->rootAliases[] = $updateClause->aliasIdentificationVariable; return $sql . ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems)); } /** * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. * * @param AST\UpdateItem $updateItem * * @return string * * @not-deprecated */ public function walkUpdateItem($updateItem) { $useTableAliasesBefore = $this->useSqlTableAliases; $this->useSqlTableAliases = false; $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; $newValue = $updateItem->newValue; switch (true) { case $newValue instanceof AST\Node: $sql .= $newValue->dispatch($this); break; case $newValue === null: $sql .= 'NULL'; break; default: $sql .= $this->conn->quote($newValue); break; } $this->useSqlTableAliases = $useTableAliasesBefore; return $sql; } /** * Walks down a WhereClause AST node, thereby generating the appropriate SQL. * WhereClause or not, the appropriate discriminator sql is added. * * @param AST\WhereClause $whereClause * * @return string * * @not-deprecated */ public function walkWhereClause($whereClause) { $condSql = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : ''; $discrSql = $this->generateDiscriminatorColumnConditionSQL($this->rootAliases); if ($this->em->hasFilters()) { $filterClauses = []; foreach ($this->rootAliases as $dqlAlias) { $class = $this->getMetadataForDqlAlias($dqlAlias); $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias); if ($filterExpr) { $filterClauses[] = $filterExpr; } } if (count($filterClauses)) { if ($condSql) { $condSql = '(' . $condSql . ') AND '; } $condSql .= implode(' AND ', $filterClauses); } } if ($condSql) { return ' WHERE ' . (! $discrSql ? $condSql : '(' . $condSql . ') AND ' . $discrSql); } if ($discrSql) { return ' WHERE ' . $discrSql; } return ''; } /** * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalExpression $condExpr * * @return string * * @not-deprecated */ public function walkConditionalExpression($condExpr) { // Phase 2 AST optimization: Skip processing of ConditionalExpression // if only one ConditionalTerm is defined if (! ($condExpr instanceof AST\ConditionalExpression)) { return $this->walkConditionalTerm($condExpr); } return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms)); } /** * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalTerm $condTerm * * @return string * * @not-deprecated */ public function walkConditionalTerm($condTerm) { // Phase 2 AST optimization: Skip processing of ConditionalTerm // if only one ConditionalFactor is defined if (! ($condTerm instanceof AST\ConditionalTerm)) { return $this->walkConditionalFactor($condTerm); } return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors)); } /** * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalFactor $factor * * @return string The SQL. * * @not-deprecated */ public function walkConditionalFactor($factor) { // Phase 2 AST optimization: Skip processing of ConditionalFactor // if only one ConditionalPrimary is defined return ! ($factor instanceof AST\ConditionalFactor) ? $this->walkConditionalPrimary($factor) : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); } /** * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. * * @param AST\ConditionalPrimary $primary * * @return string * * @not-deprecated */ public function walkConditionalPrimary($primary) { if ($primary->isSimpleConditionalExpression()) { return $primary->simpleConditionalExpression->dispatch($this); } if ($primary->isConditionalExpression()) { $condExpr = $primary->conditionalExpression; return '(' . $this->walkConditionalExpression($condExpr) . ')'; } } /** * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. * * @param AST\ExistsExpression $existsExpr * * @return string * * @not-deprecated */ public function walkExistsExpression($existsExpr) { $sql = $existsExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; return $sql; } /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * * @param AST\CollectionMemberExpression $collMemberExpr * * @return string * * @not-deprecated */ public function walkCollectionMemberExpression($collMemberExpr) { $sql = $collMemberExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (SELECT 1 FROM '; $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; assert($collPathExpr->field !== null); $fieldName = $collPathExpr->field; $dqlAlias = $collPathExpr->identificationVariable; $class = $this->getMetadataForDqlAlias($dqlAlias); switch (true) { // InputParameter case $entityExpr instanceof AST\InputParameter: $dqlParamKey = $entityExpr->name; $entitySql = '?'; break; // SingleValuedAssociationPathExpression | IdentificationVariable case $entityExpr instanceof AST\PathExpression: $entitySql = $this->walkPathExpression($entityExpr); break; default: throw new BadMethodCallException('Not implemented'); } $assoc = $class->associationMappings[$fieldName]; if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) { $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName()); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; $sqlParts = []; foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform); $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } $sql .= implode(' AND ', $sqlParts); } else { // many-to-many $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; $joinTable = $owningAssoc['joinTable']; // SQL table aliases $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; $sqlParts = []; foreach ($joinColumns as $joinColumn) { $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform); $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn; } $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; foreach ($joinColumns as $joinColumn) { if (isset($dqlParamKey)) { $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++); } $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' IN (' . $entitySql . ')'; } $sql .= implode(' AND ', $sqlParts); } return $sql . ')'; } /** * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\EmptyCollectionComparisonExpression $emptyCollCompExpr * * @return string * * @not-deprecated */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { $sizeFunc = new AST\Functions\SizeFunction('size'); $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); } /** * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\NullComparisonExpression $nullCompExpr * * @return string * * @not-deprecated */ public function walkNullComparisonExpression($nullCompExpr) { $expression = $nullCompExpr->expression; $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; // Handle ResultVariable if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) { return $this->walkResultVariable($expression) . $comparison; } // Handle InputParameter mapping inclusion to ParserResult if ($expression instanceof AST\InputParameter) { return $this->walkInputParameter($expression) . $comparison; } return $expression->dispatch($this) . $comparison; } /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. * * @deprecated Use {@see walkInListExpression()} or {@see walkInSubselectExpression()} instead. * * @param AST\InExpression $inExpr * * @return string */ public function walkInExpression($inExpr) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10267', '%s() is deprecated, call walkInListExpression() or walkInSubselectExpression() instead.', __METHOD__ ); if ($inExpr instanceof AST\InListExpression) { return $this->walkInListExpression($inExpr); } if ($inExpr instanceof AST\InSubselectExpression) { return $this->walkInSubselectExpression($inExpr); } $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; $sql .= $inExpr->subselect ? $this->walkSubselect($inExpr->subselect) : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals)); $sql .= ')'; return $sql; } /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. */ public function walkInListExpression(AST\InListExpression $inExpr): string { return $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (' . implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals)) . ')'; } /** * Walks down an InExpression AST node, thereby generating the appropriate SQL. */ public function walkInSubselectExpression(AST\InSubselectExpression $inExpr): string { return $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (' . $this->walkSubselect($inExpr->subselect) . ')'; } /** * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. * * @param AST\InstanceOfExpression $instanceOfExpr * * @return string * * @throws QueryException * * @not-deprecated */ public function walkInstanceOfExpression($instanceOfExpr) { $sql = ''; $dqlAlias = $instanceOfExpr->identificationVariable; $discrClass = $class = $this->getMetadataForDqlAlias($dqlAlias); if ($class->discriminatorColumn) { $discrClass = $this->em->getClassMetadata($class->rootEntityName); } if ($this->useSqlTableAliases) { $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.'; } $sql .= $class->getDiscriminatorColumn()['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN '); $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr); return $sql; } /** * @param mixed $inParam * * @return string * * @not-deprecated */ public function walkInParameter($inParam) { return $inParam instanceof AST\InputParameter ? $this->walkInputParameter($inParam) : $this->walkArithmeticExpression($inParam); } /** * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. * * @param AST\Literal $literal * * @return string * * @not-deprecated */ public function walkLiteral($literal) { switch ($literal->type) { case AST\Literal::STRING: return $this->conn->quote($literal->value); case AST\Literal::BOOLEAN: return (string) $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true'); case AST\Literal::NUMERIC: return (string) $literal->value; default: throw QueryException::invalidLiteral($literal); } } /** * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. * * @param AST\BetweenExpression $betweenExpr * * @return string * * @not-deprecated */ public function walkBetweenExpression($betweenExpr) { $sql = $this->walkArithmeticExpression($betweenExpr->expression); if ($betweenExpr->not) { $sql .= ' NOT'; } $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); return $sql; } /** * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. * * @param AST\LikeExpression $likeExpr * * @return string * * @not-deprecated */ public function walkLikeExpression($likeExpr) { $stringExpr = $likeExpr->stringExpression; if (is_string($stringExpr)) { if (! isset($this->queryComponents[$stringExpr]['resultVariable'])) { throw new LogicException(sprintf('No result variable found for string expression "%s".', $stringExpr)); } $leftExpr = $this->walkResultVariable($stringExpr); } else { $leftExpr = $stringExpr->dispatch($this); } $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; if ($likeExpr->stringPattern instanceof AST\InputParameter) { $sql .= $this->walkInputParameter($likeExpr->stringPattern); } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) { $sql .= $this->walkFunction($likeExpr->stringPattern); } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) { $sql .= $this->walkPathExpression($likeExpr->stringPattern); } else { $sql .= $this->walkLiteral($likeExpr->stringPattern); } if ($likeExpr->escapeChar) { $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar); } return $sql; } /** * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. * * @param AST\PathExpression $stateFieldPathExpression * * @return string * * @not-deprecated */ public function walkStateFieldPathExpression($stateFieldPathExpression) { return $this->walkPathExpression($stateFieldPathExpression); } /** * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. * * @param AST\ComparisonExpression $compExpr * * @return string * * @not-deprecated */ public function walkComparisonExpression($compExpr) { $leftExpr = $compExpr->leftExpression; $rightExpr = $compExpr->rightExpression; $sql = ''; $sql .= $leftExpr instanceof AST\Node ? $leftExpr->dispatch($this) : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr)); $sql .= ' ' . $compExpr->operator . ' '; $sql .= $rightExpr instanceof AST\Node ? $rightExpr->dispatch($this) : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr)); return $sql; } /** * Walks down an InputParameter AST node, thereby generating the appropriate SQL. * * @param AST\InputParameter $inputParam * * @return string * * @not-deprecated */ public function walkInputParameter($inputParam) { $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++); $parameter = $this->query->getParameter($inputParam->name); if ($parameter) { $type = $parameter->getType(); if (Type::hasType($type)) { return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform); } } return '?'; } /** * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param AST\ArithmeticExpression $arithmeticExpr * * @return string * * @not-deprecated */ public function walkArithmeticExpression($arithmeticExpr) { return $arithmeticExpr->isSimpleArithmeticExpression() ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; } /** * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. * * @param AST\SimpleArithmeticExpression $simpleArithmeticExpr * * @return string * * @not-deprecated */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) { return $this->walkArithmeticTerm($simpleArithmeticExpr); } return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms)); } /** * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. * * @param mixed $term * * @return string * * @not-deprecated */ public function walkArithmeticTerm($term) { if (is_string($term)) { return isset($this->queryComponents[$term]) ? $this->walkResultVariable($this->queryComponents[$term]['token']->value) : $term; } // Phase 2 AST optimization: Skip processing of ArithmeticTerm // if only one ArithmeticFactor is defined if (! ($term instanceof AST\ArithmeticTerm)) { return $this->walkArithmeticFactor($term); } return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors)); } /** * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $factor * * @return string * * @not-deprecated */ public function walkArithmeticFactor($factor) { if (is_string($factor)) { return isset($this->queryComponents[$factor]) ? $this->walkResultVariable($this->queryComponents[$factor]['token']->value) : $factor; } // Phase 2 AST optimization: Skip processing of ArithmeticFactor // if only one ArithmeticPrimary is defined if (! ($factor instanceof AST\ArithmeticFactor)) { return $this->walkArithmeticPrimary($factor); } $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : ''); return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary); } /** * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $primary * * @return string The SQL. * * @not-deprecated */ public function walkArithmeticPrimary($primary) { if ($primary instanceof AST\SimpleArithmeticExpression) { return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; } if ($primary instanceof AST\Node) { return $primary->dispatch($this); } return $this->walkEntityIdentificationVariable($primary); } /** * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. * * @param mixed $stringPrimary * * @return string * * @not-deprecated */ public function walkStringPrimary($stringPrimary) { return is_string($stringPrimary) ? $this->conn->quote($stringPrimary) : $stringPrimary->dispatch($this); } /** * Walks down a ResultVariable that represents an AST node, thereby generating the appropriate SQL. * * @param string $resultVariable * * @return string * * @not-deprecated */ public function walkResultVariable($resultVariable) { if (! isset($this->scalarResultAliasMap[$resultVariable])) { throw new InvalidArgumentException(sprintf('Unknown result variable: %s', $resultVariable)); } $resultAlias = $this->scalarResultAliasMap[$resultVariable]; if (is_array($resultAlias)) { return implode(', ', $resultAlias); } return $resultAlias; } /** * @return string The list in parentheses of valid child discriminators from the given class * * @throws QueryException */ private function getChildDiscriminatorsFromClassMetadata( ClassMetadata $rootClass, AST\InstanceOfExpression $instanceOfExpr ): string { $sqlParameterList = []; $discriminators = []; foreach ($instanceOfExpr->value as $parameter) { if ($parameter instanceof AST\InputParameter) { $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name; $sqlParameterList[] = $this->walkInParameter($parameter); continue; } $metadata = $this->em->getClassMetadata($parameter); if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) { throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name); } $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em); } foreach (array_keys($discriminators) as $dis) { $sqlParameterList[] = $this->conn->quote($dis); } return '(' . implode(', ', $sqlParameterList) . ')'; } } orm/lib/Doctrine/ORM/Query/TreeWalker.php 0000644 00000034327 15120025736 0014210 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\ORM\AbstractQuery; /** * Interface for walkers of DQL ASTs (abstract syntax trees). * * @psalm-import-type QueryComponent from Parser */ interface TreeWalker { /** * Initializes TreeWalker with important information about the ASTs to be walked. * * @param AbstractQuery $query The parsed Query. * @param ParserResult $parserResult The result of the parsing process. * @param mixed[] $queryComponents The query components (symbol table). * @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table). */ public function __construct($query, $parserResult, array $queryComponents); /** * Returns internal queryComponents array. * * @return array<string, array<string, mixed>> * @psalm-return array<string, QueryComponent> */ public function getQueryComponents(); /** * Sets or overrides a query component for a given dql alias. * * @deprecated This method will be removed from the interface in 3.0. * * @param string $dqlAlias The DQL alias. * @param array<string, mixed> $queryComponent * @psalm-param QueryComponent $queryComponent * * @return void */ public function setQueryComponent($dqlAlias, array $queryComponent); /** * Walks down a SelectStatement AST node. * * @return void */ public function walkSelectStatement(AST\SelectStatement $AST); /** * Walks down a SelectClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SelectClause $selectClause * * @return void */ public function walkSelectClause($selectClause); /** * Walks down a FromClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\FromClause $fromClause * * @return void */ public function walkFromClause($fromClause); /** * Walks down a FunctionNode AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\Functions\FunctionNode $function * * @return void */ public function walkFunction($function); /** * Walks down an OrderByClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\OrderByClause $orderByClause * * @return void */ public function walkOrderByClause($orderByClause); /** * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\OrderByItem $orderByItem * * @return void */ public function walkOrderByItem($orderByItem); /** * Walks down a HavingClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\HavingClause $havingClause * * @return void */ public function walkHavingClause($havingClause); /** * Walks down a Join AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\Join $join * * @return void */ public function walkJoin($join); /** * Walks down a SelectExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SelectExpression $selectExpression * * @return void */ public function walkSelectExpression($selectExpression); /** * Walks down a QuantifiedExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\QuantifiedExpression $qExpr * * @return void */ public function walkQuantifiedExpression($qExpr); /** * Walks down a Subselect AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\Subselect $subselect * * @return void */ public function walkSubselect($subselect); /** * Walks down a SubselectFromClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SubselectFromClause $subselectFromClause * * @return void */ public function walkSubselectFromClause($subselectFromClause); /** * Walks down a SimpleSelectClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SimpleSelectClause $simpleSelectClause * * @return void */ public function walkSimpleSelectClause($simpleSelectClause); /** * Walks down a SimpleSelectExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SimpleSelectExpression $simpleSelectExpression * * @return void */ public function walkSimpleSelectExpression($simpleSelectExpression); /** * Walks down an AggregateExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\AggregateExpression $aggExpression * * @return void */ public function walkAggregateExpression($aggExpression); /** * Walks down a GroupByClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\GroupByClause $groupByClause * * @return void */ public function walkGroupByClause($groupByClause); /** * Walks down a GroupByItem AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\PathExpression|string $groupByItem * * @return void */ public function walkGroupByItem($groupByItem); /** * Walks down an UpdateStatement AST node. * * @return void */ public function walkUpdateStatement(AST\UpdateStatement $AST); /** * Walks down a DeleteStatement AST node. * * @return void */ public function walkDeleteStatement(AST\DeleteStatement $AST); /** * Walks down a DeleteClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @return void */ public function walkDeleteClause(AST\DeleteClause $deleteClause); /** * Walks down an UpdateClause AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\UpdateClause $updateClause * * @return void */ public function walkUpdateClause($updateClause); /** * Walks down an UpdateItem AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\UpdateItem $updateItem * * @return void */ public function walkUpdateItem($updateItem); /** * Walks down a WhereClause AST node. * * WhereClause or not, the appropriate discriminator sql is added. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\WhereClause $whereClause * * @return void */ public function walkWhereClause($whereClause); /** * Walk down a ConditionalExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ConditionalExpression $condExpr * * @return void */ public function walkConditionalExpression($condExpr); /** * Walks down a ConditionalTerm AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ConditionalTerm $condTerm * * @return void */ public function walkConditionalTerm($condTerm); /** * Walks down a ConditionalFactor AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ConditionalFactor $factor * * @return void */ public function walkConditionalFactor($factor); /** * Walks down a ConditionalPrimary AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ConditionalPrimary $primary * * @return void */ public function walkConditionalPrimary($primary); /** * Walks down an ExistsExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ExistsExpression $existsExpr * * @return void */ public function walkExistsExpression($existsExpr); /** * Walks down a CollectionMemberExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\CollectionMemberExpression $collMemberExpr * * @return void */ public function walkCollectionMemberExpression($collMemberExpr); /** * Walks down an EmptyCollectionComparisonExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\EmptyCollectionComparisonExpression $emptyCollCompExpr * * @return void */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr); /** * Walks down a NullComparisonExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\NullComparisonExpression $nullCompExpr * * @return void */ public function walkNullComparisonExpression($nullCompExpr); /** * Walks down an InExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\InExpression $inExpr * * @return void */ public function walkInExpression($inExpr); /** * Walks down an InstanceOfExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\InstanceOfExpression $instanceOfExpr * * @return void */ public function walkInstanceOfExpression($instanceOfExpr); /** * Walks down a literal that represents an AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\Literal $literal * * @return void */ public function walkLiteral($literal); /** * Walks down a BetweenExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\BetweenExpression $betweenExpr * * @return void */ public function walkBetweenExpression($betweenExpr); /** * Walks down a LikeExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\LikeExpression $likeExpr * * @return void */ public function walkLikeExpression($likeExpr); /** * Walks down a StateFieldPathExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\PathExpression $stateFieldPathExpression * * @return void */ public function walkStateFieldPathExpression($stateFieldPathExpression); /** * Walks down a ComparisonExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ComparisonExpression $compExpr * * @return void */ public function walkComparisonExpression($compExpr); /** * Walks down an InputParameter AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\InputParameter $inputParam * * @return void */ public function walkInputParameter($inputParam); /** * Walks down an ArithmeticExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\ArithmeticExpression $arithmeticExpr * * @return void */ public function walkArithmeticExpression($arithmeticExpr); /** * Walks down an ArithmeticTerm AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param mixed $term * * @return void */ public function walkArithmeticTerm($term); /** * Walks down a StringPrimary that represents an AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param mixed $stringPrimary * * @return void */ public function walkStringPrimary($stringPrimary); /** * Walks down an ArithmeticFactor that represents an AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param mixed $factor * * @return void */ public function walkArithmeticFactor($factor); /** * Walks down an SimpleArithmeticExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\SimpleArithmeticExpression $simpleArithmeticExpr * * @return void */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr); /** * Walks down a PathExpression AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\PathExpression $pathExpr * * @return void */ public function walkPathExpression($pathExpr); /** * Walks down a ResultVariable that represents an AST node. * * @deprecated This method will be removed from the interface in 3.0. * * @param string $resultVariable * * @return void */ public function walkResultVariable($resultVariable); /** * Gets an executor that can be used to execute the result of this walker. * * @deprecated This method will be removed from the interface in 3.0. * * @param AST\DeleteStatement|AST\UpdateStatement|AST\SelectStatement $AST * * @return Exec\AbstractSqlExecutor */ public function getExecutor($AST); } orm/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php 0000644 00000052742 15120025736 0015512 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Mapping\ClassMetadata; use LogicException; use function array_diff; use function array_keys; use function debug_backtrace; use function is_a; use function sprintf; use const DEBUG_BACKTRACE_IGNORE_ARGS; /** * An adapter implementation of the TreeWalker interface. The methods in this class * are empty. This class exists as convenience for creating tree walkers. * * @psalm-import-type QueryComponent from Parser */ abstract class TreeWalkerAdapter implements TreeWalker { /** * The original Query. * * @var AbstractQuery */ private $query; /** * The ParserResult of the original query that was produced by the Parser. * * @var ParserResult */ private $parserResult; /** * The query components of the original query (the "symbol table") that was produced by the Parser. * * @psalm-var array<string, QueryComponent> */ private $queryComponents; /** * {@inheritDoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->query = $query; $this->parserResult = $parserResult; $this->queryComponents = $queryComponents; } /** * {@inheritDoc} */ public function getQueryComponents() { return $this->queryComponents; } /** * Sets or overrides a query component for a given dql alias. * * @internal This method will be protected in 3.0. * * @param string $dqlAlias The DQL alias. * @param array<string, mixed> $queryComponent * @psalm-param QueryComponent $queryComponent * * @return void */ public function setQueryComponent($dqlAlias, array $queryComponent) { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); if (! isset($trace[1]['class']) || ! is_a($trace[1]['class'], self::class, true)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method %s will be protected in 3.0. Calling it publicly is deprecated.', __METHOD__ ); } $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token']; if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->queryComponents[$dqlAlias] = $queryComponent; } /** * Returns internal queryComponents array. * * @deprecated Call {@see getQueryComponents()} instead. * * @return array<string, array<string, mixed>> * @psalm-return array<string, QueryComponent> */ protected function _getQueryComponents() { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method %s is deprecated, call getQueryComponents() instead.', __METHOD__ ); return $this->queryComponents; } /** * Retrieves the Query Instance responsible for the current walkers execution. * * @return AbstractQuery */ protected function _getQuery() { return $this->query; } /** * Retrieves the ParserResult. * * @return ParserResult */ protected function _getParserResult() { return $this->parserResult; } public function walkSelectStatement(AST\SelectStatement $AST) { } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSelectClause($selectClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkFromClause($fromClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkFunction($function) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkOrderByClause($orderByClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkOrderByItem($orderByItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkHavingClause($havingClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkJoin($join) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSelectExpression($selectExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkQuantifiedExpression($qExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSubselect($subselect) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSubselectFromClause($subselectFromClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleSelectClause($simpleSelectClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleSelectExpression($simpleSelectExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkAggregateExpression($aggExpression) { } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkGroupByClause($groupByClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkGroupByItem($groupByItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } public function walkUpdateStatement(AST\UpdateStatement $AST) { } public function walkDeleteStatement(AST\DeleteStatement $AST) { } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkUpdateClause($updateClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkUpdateItem($updateItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkWhereClause($whereClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalExpression($condExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalTerm($condTerm) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalFactor($factor) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalPrimary($primary) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkExistsExpression($existsExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkCollectionMemberExpression($collMemberExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkNullComparisonExpression($nullCompExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInExpression($inExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInstanceOfExpression($instanceOfExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkLiteral($literal) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkBetweenExpression($betweenExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkLikeExpression($likeExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkStateFieldPathExpression($stateFieldPathExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkComparisonExpression($compExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInputParameter($inputParam) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticExpression($arithmeticExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticTerm($term) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkStringPrimary($stringPrimary) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticFactor($factor) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkPathExpression($pathExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkResultVariable($resultVariable) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function getExecutor($AST) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); return null; } final protected function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata { $metadata = $this->_getQueryComponents()[$dqlAlias]['metadata'] ?? null; if ($metadata === null) { throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias)); } return $metadata; } } orm/lib/Doctrine/ORM/Query/TreeWalkerChain.php 0000644 00000063352 15120025736 0015153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\AbstractQuery; use Generator; use function array_diff; use function array_keys; /** * Represents a chain of tree walkers that modify an AST and finally emit output. * Only the last walker in the chain can emit output. Any previous walkers can modify * the AST to influence the final output produced by the last walker. * * @psalm-import-type QueryComponent from Parser */ class TreeWalkerChain implements TreeWalker { /** * The tree walkers. * * @var string[] * @psalm-var list<class-string<TreeWalker>> */ private $walkers = []; /** @var AbstractQuery */ private $query; /** @var ParserResult */ private $parserResult; /** * The query components of the original query (the "symbol table") that was produced by the Parser. * * @var array<string, array<string, mixed>> * @psalm-var array<string, QueryComponent> */ private $queryComponents; /** * Returns the internal queryComponents array. * * {@inheritDoc} */ public function getQueryComponents() { return $this->queryComponents; } /** * {@inheritDoc} * * @return void */ public function setQueryComponent($dqlAlias, array $queryComponent) { $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token']; if (array_diff($requiredKeys, array_keys($queryComponent))) { throw QueryException::invalidQueryComponent($dqlAlias); } $this->queryComponents[$dqlAlias] = $queryComponent; } /** * {@inheritDoc} */ public function __construct($query, $parserResult, array $queryComponents) { $this->query = $query; $this->parserResult = $parserResult; $this->queryComponents = $queryComponents; } /** * Adds a tree walker to the chain. * * @param string $walkerClass The class of the walker to instantiate. * @psalm-param class-string<TreeWalker> $walkerClass * * @return void */ public function addTreeWalker($walkerClass) { $this->walkers[] = $walkerClass; } public function walkSelectStatement(AST\SelectStatement $AST) { foreach ($this->getWalkers() as $walker) { $walker->walkSelectStatement($AST); $this->queryComponents = $walker->getQueryComponents(); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSelectClause($selectClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSelectClause($selectClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkFromClause($fromClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkFromClause($fromClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkFunction($function) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkFunction($function); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkOrderByClause($orderByClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkOrderByClause($orderByClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkOrderByItem($orderByItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkOrderByItem($orderByItem); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkHavingClause($havingClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkHavingClause($havingClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkJoin($join) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkJoin($join); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSelectExpression($selectExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSelectExpression($selectExpression); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkQuantifiedExpression($qExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkQuantifiedExpression($qExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSubselect($subselect) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSubselect($subselect); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSubselectFromClause($subselectFromClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSubselectFromClause($subselectFromClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleSelectClause($simpleSelectClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSimpleSelectClause($simpleSelectClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleSelectExpression($simpleSelectExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSimpleSelectExpression($simpleSelectExpression); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkAggregateExpression($aggExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkAggregateExpression($aggExpression); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkGroupByClause($groupByClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkGroupByClause($groupByClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkGroupByItem($groupByItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkGroupByItem($groupByItem); } } public function walkUpdateStatement(AST\UpdateStatement $AST) { foreach ($this->getWalkers() as $walker) { $walker->walkUpdateStatement($AST); } } public function walkDeleteStatement(AST\DeleteStatement $AST) { foreach ($this->getWalkers() as $walker) { $walker->walkDeleteStatement($AST); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkDeleteClause(AST\DeleteClause $deleteClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkDeleteClause($deleteClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkUpdateClause($updateClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkUpdateClause($updateClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkUpdateItem($updateItem) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkUpdateItem($updateItem); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkWhereClause($whereClause) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkWhereClause($whereClause); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalExpression($condExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkConditionalExpression($condExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalTerm($condTerm) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkConditionalTerm($condTerm); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalFactor($factor) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkConditionalFactor($factor); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkConditionalPrimary($condPrimary) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkConditionalPrimary($condPrimary); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkExistsExpression($existsExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkExistsExpression($existsExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkCollectionMemberExpression($collMemberExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkCollectionMemberExpression($collMemberExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkEmptyCollectionComparisonExpression($emptyCollCompExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkNullComparisonExpression($nullCompExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkNullComparisonExpression($nullCompExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInExpression($inExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkInExpression($inExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInstanceOfExpression($instanceOfExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkInstanceOfExpression($instanceOfExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkLiteral($literal) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkLiteral($literal); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkBetweenExpression($betweenExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkBetweenExpression($betweenExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkLikeExpression($likeExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkLikeExpression($likeExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkStateFieldPathExpression($stateFieldPathExpression) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkStateFieldPathExpression($stateFieldPathExpression); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkComparisonExpression($compExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkComparisonExpression($compExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkInputParameter($inputParam) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkInputParameter($inputParam); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticExpression($arithmeticExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkArithmeticExpression($arithmeticExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticTerm($term) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkArithmeticTerm($term); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkStringPrimary($stringPrimary) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkStringPrimary($stringPrimary); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkArithmeticFactor($factor) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkArithmeticFactor($factor); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkSimpleArithmeticExpression($simpleArithmeticExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkSimpleArithmeticExpression($simpleArithmeticExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkPathExpression($pathExpr) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkPathExpression($pathExpr); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function walkResultVariable($resultVariable) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); foreach ($this->getWalkers() as $walker) { $walker->walkResultVariable($resultVariable); } } /** * {@inheritDoc} * * @deprecated This method will be removed in 3.0. */ public function getExecutor($AST) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9551', 'Method "%s" is deprecated and will be removed in ORM 3.0 without replacement.', __METHOD__ ); return null; } /** @psalm-return Generator<int, TreeWalker> */ private function getWalkers(): Generator { foreach ($this->walkers as $walkerClass) { yield new $walkerClass($this->query, $this->parserResult, $this->queryComponents); } } } orm/lib/Doctrine/ORM/Query/TreeWalkerChainIterator.php 0000644 00000006732 15120025736 0016664 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Query; use ArrayAccess; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\AbstractQuery; use Iterator; use ReturnTypeWillChange; use function key; use function next; use function reset; /** * @deprecated This class will be removed in 3.0 without replacement. * * @template-implements Iterator<TreeWalker> * @template-implements ArrayAccess<int, TreeWalker> */ class TreeWalkerChainIterator implements Iterator, ArrayAccess { /** @var class-string<TreeWalker>[] */ private $walkers = []; /** @var TreeWalkerChain */ private $treeWalkerChain; /** @var AbstractQuery */ private $query; /** @var ParserResult */ private $parserResult; /** * @param AbstractQuery $query * @param ParserResult $parserResult */ public function __construct(TreeWalkerChain $treeWalkerChain, $query, $parserResult) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9511', '%s is deprecated and will be removed without replacement.', self::class ); $this->treeWalkerChain = $treeWalkerChain; $this->query = $query; $this->parserResult = $parserResult; } /** * @return string|false * @psalm-return class-string<TreeWalker>|false */ #[ReturnTypeWillChange] public function rewind() { return reset($this->walkers); } /** @return TreeWalker|null */ #[ReturnTypeWillChange] public function current() { return $this->offsetGet(key($this->walkers)); } /** @return int */ #[ReturnTypeWillChange] public function key() { return key($this->walkers); } /** @return TreeWalker|null */ #[ReturnTypeWillChange] public function next() { next($this->walkers); return $this->offsetGet(key($this->walkers)); } /** * {@inheritDoc} * * @return bool */ #[ReturnTypeWillChange] public function valid() { return key($this->walkers) !== null; } /** * @param mixed $offset * @psalm-param array-key|null $offset * * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->walkers[$offset ?? '']); } /** * @param mixed $offset * @psalm-param array-key|null $offset * * @return TreeWalker|null */ #[ReturnTypeWillChange] public function offsetGet($offset) { if ($this->offsetExists($offset)) { return new $this->walkers[$offset]( $this->query, $this->parserResult, $this->treeWalkerChain->getQueryComponents() ); } return null; } /** * {@inheritDoc} * * @param string $value * @psalm-param array-key|null $offset * * @return void */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { $this->walkers[] = $value; } else { $this->walkers[$offset] = $value; } } /** * @param mixed $offset * @psalm-param array-key|null $offset * * @return void */ #[ReturnTypeWillChange] public function offsetUnset($offset) { if ($this->offsetExists($offset)) { unset($this->walkers[$offset ?? '']); } } } orm/lib/Doctrine/ORM/Repository/Exception/InvalidFindByCall.php 0000644 00000001217 15120025736 0020421 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Repository\Exception; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\RepositoryException; final class InvalidFindByCall extends ORMException implements RepositoryException { public static function fromInverseSideUsage( string $entityName, string $associationFieldName ): self { return new self( "You cannot search for the association field '" . $entityName . '#' . $associationFieldName . "', " . 'because it is the inverse side of an association. Find methods only work on owning side associations.' ); } } orm/lib/Doctrine/ORM/Repository/Exception/InvalidMagicMethodCall.php 0000644 00000001415 15120025736 0021427 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Repository\Exception; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\RepositoryException; final class InvalidMagicMethodCall extends ORMException implements RepositoryException { public static function becauseFieldNotFoundIn( string $entityName, string $fieldName, string $method ): self { return new self( "Entity '" . $entityName . "' has no field '" . $fieldName . "'. " . "You can therefore not call '" . $method . "' on the entities' repository." ); } public static function onMissingParameter(string $methodName): self { return new self("You need to pass a parameter to '" . $methodName . "'"); } } orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php 0000644 00000004261 15120025736 0020223 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Repository; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ObjectRepository; use function spl_object_id; /** * This factory is used to create default repository objects for entities at runtime. */ final class DefaultRepositoryFactory implements RepositoryFactory { /** * The list of EntityRepository instances. * * @var ObjectRepository[] * @psalm-var array<string, ObjectRepository> */ private $repositoryList = []; /** * {@inheritDoc} */ public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository { $repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_id($entityManager); if (isset($this->repositoryList[$repositoryHash])) { return $this->repositoryList[$repositoryHash]; } return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName); } /** * Create a new repository instance for an entity class. * * @param EntityManagerInterface $entityManager The EntityManager instance. * @param string $entityName The name of the entity. */ private function createRepository( EntityManagerInterface $entityManager, string $entityName ): ObjectRepository { $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); $repository = new $repositoryClassName($entityManager, $metadata); if (! $repository instanceof EntityRepository) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9533', 'Configuring %s as repository class is deprecated because it does not extend %s.', $repositoryClassName, EntityRepository::class ); } return $repository; } } orm/lib/Doctrine/ORM/Repository/RepositoryFactory.php 0000644 00000001257 15120025736 0016720 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Repository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectRepository; /** * Interface for entity repository factory. */ interface RepositoryFactory { /** * Gets the repository for an entity class. * * @param EntityManagerInterface $entityManager The EntityManager instance. * @param class-string<T> $entityName The name of the entity. * * @return ObjectRepository<T> This type will change to {@see EntityRepository} in 3.0. * * @template T of object */ public function getRepository(EntityManagerInterface $entityManager, $entityName); } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/CollectionRegionCommand.php 0000644 00000011052 15120025736 0023614 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\ORM\Cache; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; /** * Command to clear a collection cache region. */ class CollectionRegionCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:region:collection') ->setDescription('Clear a second-level cache collection region') ->addArgument('owner-class', InputArgument::OPTIONAL, 'The owner entity name.') ->addArgument('association', InputArgument::OPTIONAL, 'The association collection name.') ->addArgument('owner-id', InputArgument::OPTIONAL, 'The owner identifier.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear a second-level cache collection regions for an associated Entity Manager. It is possible to delete/invalidate all collection region, a specific collection region or flushes the cache provider. The execution type differ on how you execute the command. If you want to invalidate all entries for an collection region this command would do the work: <info>%command.name% 'Entities\MyEntity' 'collectionName'</info> To invalidate a specific entry you should use : <info>%command.name% 'Entities\MyEntity' 'collectionName' 1</info> If you want to invalidate all entries for the all collection regions: <info>%command.name% --all</info> Alternatively, if you want to flush the configured cache provider for an collection region use this command: <info>%command.name% 'Entities\MyEntity' 'collectionName' --flush</info> Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $ownerClass = $input->getArgument('owner-class'); $assoc = $input->getArgument('association'); $ownerId = $input->getArgument('owner-id'); $cache = $em->getCache(); if (! $cache instanceof Cache) { throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); } if (( ! $ownerClass || ! $assoc) && ! $input->getOption('all')) { throw new InvalidArgumentException('Missing arguments "--owner-class" "--association"'); } if ($input->getOption('flush')) { $cache->getCollectionCacheRegion($ownerClass, $assoc) ->evictAll(); $ui->comment( sprintf( 'Flushing cache provider configured for <info>"%s#%s"</info>', $ownerClass, $assoc ) ); return 0; } if ($input->getOption('all')) { $ui->comment('Clearing <info>all</info> second-level cache collection regions'); $cache->evictEntityRegions(); return 0; } if ($ownerId) { $ui->comment( sprintf( 'Clearing second-level cache entry for collection <info>"%s#%s"</info> owner entity identified by <info>"%s"</info>', $ownerClass, $assoc, $ownerId ) ); $cache->evictCollection($ownerClass, $assoc, $ownerId); return 0; } $ui->comment(sprintf('Clearing second-level cache for collection <info>"%s#%s"</info>', $ownerClass, $assoc)); $cache->evictCollectionRegion($ownerClass, $assoc); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/EntityRegionCommand.php 0000644 00000010112 15120025736 0022771 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\ORM\Cache; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; /** * Command to clear a entity cache region. */ class EntityRegionCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:region:entity') ->setDescription('Clear a second-level cache entity region') ->addArgument('entity-class', InputArgument::OPTIONAL, 'The entity name.') ->addArgument('entity-id', InputArgument::OPTIONAL, 'The entity identifier.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all entity regions will be deleted/invalidated.') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear a second-level cache entity region for an associated Entity Manager. It is possible to delete/invalidate all entity region, a specific entity region or flushes the cache provider. The execution type differ on how you execute the command. If you want to invalidate all entries for an entity region this command would do the work: <info>%command.name% 'Entities\MyEntity'</info> To invalidate a specific entry you should use : <info>%command.name% 'Entities\MyEntity' 1</info> If you want to invalidate all entries for the all entity regions: <info>%command.name% --all</info> Alternatively, if you want to flush the configured cache provider for an entity region use this command: <info>%command.name% 'Entities\MyEntity' --flush</info> Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $entityClass = $input->getArgument('entity-class'); $entityId = $input->getArgument('entity-id'); $cache = $em->getCache(); if (! $cache instanceof Cache) { throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); } if (! $entityClass && ! $input->getOption('all')) { throw new InvalidArgumentException('Invalid argument "--entity-class"'); } if ($input->getOption('flush')) { $cache->getEntityCacheRegion($entityClass) ->evictAll(); $ui->comment(sprintf('Flushing cache provider configured for entity named <info>"%s"</info>', $entityClass)); return 0; } if ($input->getOption('all')) { $ui->comment('Clearing <info>all</info> second-level cache entity regions'); $cache->evictEntityRegions(); return 0; } if ($entityId) { $ui->comment( sprintf( 'Clearing second-level cache entry for entity <info>"%s"</info> identified by <info>"%s"</info>', $entityClass, $entityId ) ); $cache->evictEntity($entityClass, $entityId); return 0; } $ui->comment(sprintf('Clearing second-level cache for entity <info>"%s"</info>', $entityClass)); $cache->evictEntityRegion($entityClass); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/MetadataCommand.php 0000644 00000003664 15120025736 0022107 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** * Command to clear the metadata cache of the various cache drivers. * * @link www.doctrine-project.org */ class MetadataCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:metadata') ->setDescription('Clear all metadata cache of the various cache drivers') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear the metadata cache of associated Entity Manager. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $cacheDriver = $em->getConfiguration()->getMetadataCache(); if (! $cacheDriver) { throw new InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.'); } $ui->comment('Clearing <info>all</info> Metadata cache entries'); $result = $cacheDriver->clear(); $message = $result ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; $ui->success($message); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryCommand.php 0000644 00000010126 15120025736 0021463 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\Common\Cache\ApcCache; use Doctrine\Common\Cache\ClearableCache; use Doctrine\Common\Cache\FlushableCache; use Doctrine\Common\Cache\XcacheCache; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use LogicException; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function get_debug_type; use function sprintf; /** * Command to clear the query cache of the various cache drivers. * * @link www.doctrine-project.org */ class QueryCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:query') ->setDescription('Clear all query cache of the various cache drivers') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear the query cache of associated Entity Manager. It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider instance completely. The execution type differ on how you execute the command. If you want to invalidate the entries (and not delete from cache instance), this command would do the work: <info>%command.name%</info> Alternatively, if you want to flush the cache provider using this command: <info>%command.name% --flush</info> Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $cache = $em->getConfiguration()->getQueryCache(); $cacheDriver = $em->getConfiguration()->getQueryCacheImpl(); if (! $cacheDriver) { throw new InvalidArgumentException('No Query cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof ApcCache || $cache instanceof ApcuAdapter) { throw new LogicException('Cannot clear APCu Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.'); } if ($cacheDriver instanceof XcacheCache) { throw new LogicException('Cannot clear XCache Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.'); } if (! ($cacheDriver instanceof ClearableCache)) { throw new LogicException(sprintf( 'Can only clear cache when ClearableCache interface is implemented, %s does not implement.', get_debug_type($cacheDriver) )); } $ui->comment('Clearing <info>all</info> Query cache entries'); $result = $cache ? $cache->clear() : $cacheDriver->deleteAll(); $message = $result ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; if ($input->getOption('flush') === true && ! $cache) { if (! ($cacheDriver instanceof FlushableCache)) { throw new LogicException(sprintf( 'Can only clear cache when FlushableCache interface is implemented, %s does not implement.', get_debug_type($cacheDriver) )); } $result = $cacheDriver->flushAll(); $message = $result ? 'Successfully flushed cache entries.' : $message; } $ui->success($message); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/QueryRegionCommand.php 0000644 00000007075 15120025736 0022640 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\ORM\Cache; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; /** * Command to clear a query cache region. */ class QueryRegionCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:region:query') ->setDescription('Clear a second-level cache query region') ->addArgument('region-name', InputArgument::OPTIONAL, 'The query region to clear.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('all', null, InputOption::VALUE_NONE, 'If defined, all query regions will be deleted/invalidated.') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, all cache entries will be flushed.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear a second-level cache query region for an associated Entity Manager. It is possible to delete/invalidate all query region, a specific query region or flushes the cache provider. The execution type differ on how you execute the command. If you want to invalidate all entries for the default query region this command would do the work: <info>%command.name%</info> To invalidate entries for a specific query region you should use : <info>%command.name% my_region_name</info> If you want to invalidate all entries for the all query region: <info>%command.name% --all</info> Alternatively, if you want to flush the configured cache provider use this command: <info>%command.name% my_region_name --flush</info> Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $name = $input->getArgument('region-name'); $cache = $em->getCache(); if ($name === null) { $name = Cache::DEFAULT_QUERY_REGION_NAME; } if (! $cache instanceof Cache) { throw new InvalidArgumentException('No second-level cache is configured on the given EntityManager.'); } if ($input->getOption('flush')) { $cache->getQueryCache($name) ->getRegion() ->evictAll(); $ui->comment( sprintf( 'Flushing cache provider configured for second-level cache query region named <info>"%s"</info>', $name ) ); return 0; } if ($input->getOption('all')) { $ui->comment('Clearing <info>all</info> second-level cache query regions'); $cache->evictQueryRegions(); return 0; } $ui->comment(sprintf('Clearing second-level cache query region named <info>"%s"</info>', $name)); $cache->evictQueryRegion($name); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ClearCache/ResultCommand.php 0000644 00000010366 15120025736 0021642 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\ClearCache; use Doctrine\Common\Cache\ApcCache; use Doctrine\Common\Cache\ClearableCache; use Doctrine\Common\Cache\FlushableCache; use Doctrine\Common\Cache\XcacheCache; use Doctrine\ORM\Configuration; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use InvalidArgumentException; use LogicException; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function get_debug_type; use function method_exists; use function sprintf; /** * Command to clear the result cache of the various cache drivers. * * @link www.doctrine-project.org */ class ResultCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:clear-cache:result') ->setDescription('Clear all result cache of the various cache drivers') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('flush', null, InputOption::VALUE_NONE, 'If defined, cache entries will be flushed instead of deleted/invalidated.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command is meant to clear the result cache of associated Entity Manager. It is possible to invalidate all cache entries at once - called delete -, or flushes the cache provider instance completely. The execution type differ on how you execute the command. If you want to invalidate the entries (and not delete from cache instance), this command would do the work: <info>%command.name%</info> Alternatively, if you want to flush the cache provider using this command: <info>%command.name% --flush</info> Finally, be aware that if <info>--flush</info> option is passed, not all cache providers are able to flush entries, because of a limitation of its execution nature. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $cache = $em->getConfiguration()->getResultCache(); $cacheDriver = method_exists(Configuration::class, 'getResultCacheImpl') ? $em->getConfiguration()->getResultCacheImpl() : null; if (! $cacheDriver && ! $cache) { throw new InvalidArgumentException('No Result cache driver is configured on given EntityManager.'); } if ($cacheDriver instanceof ApcCache || $cache instanceof ApcuAdapter) { throw new LogicException('Cannot clear APCu Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.'); } if ($cacheDriver instanceof XcacheCache) { throw new LogicException('Cannot clear XCache Cache from Console, it\'s shared in the Webserver memory and not accessible from the CLI.'); } if (! $cache && ! ($cacheDriver instanceof ClearableCache)) { throw new LogicException(sprintf( 'Can only clear cache when ClearableCache interface is implemented, %s does not implement.', get_debug_type($cacheDriver) )); } $ui->comment('Clearing <info>all</info> Result cache entries'); $result = $cache ? $cache->clear() : $cacheDriver->deleteAll(); $message = $result ? 'Successfully deleted cache entries.' : 'No cache entries were deleted.'; if ($input->getOption('flush') === true && ! $cache) { if (! ($cacheDriver instanceof FlushableCache)) { throw new LogicException(sprintf( 'Can only clear cache when FlushableCache interface is implemented, %s does not implement.', get_debug_type($cacheDriver) )); } $result = $cacheDriver->flushAll(); $message = $result ? 'Successfully flushed cache entries.' : $message; } $ui->success($message); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/AbstractCommand.php 0000644 00000002563 15120025736 0022213 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Doctrine\ORM\Tools\Console\Command\AbstractEntityManagerCommand; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** * Base class for CreateCommand, DropCommand and UpdateCommand. * * @link www.doctrine-project.org */ abstract class AbstractCommand extends AbstractEntityManagerCommand { /** * @param mixed[] $metadatas * * @return int|null Null or 0 if everything went fine, or an error code. */ abstract protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui); /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = new SymfonyStyle($input, $output); $em = $this->getEntityManager($input); $metadatas = $em->getMetadataFactory()->getAllMetadata(); if (empty($metadatas)) { $ui->getErrorStyle()->success('No Metadata Classes to process.'); return 0; } return $this->executeSchemaCommand($input, $output, new SchemaTool($em), $metadatas, $ui); } } orm/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/CreateCommand.php 0000644 00000005030 15120025736 0021643 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; /** * Command to create the database schema for a set of classes based on their mappings. * * @link www.doctrine-project.org */ class CreateCommand extends AbstractCommand { /** @return void */ protected function configure() { $this->setName('orm:schema-tool:create') ->setDescription('Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Instead of trying to apply generated SQLs into EntityManager Storage Connection, output them.') ->setHelp(<<<'EOT' Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. <comment>Hint:</comment> If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return !str_starts_with($assetName, 'audit_'); }); EOT ); } /** * {@inheritDoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui) { $dumpSql = $input->getOption('dump-sql') === true; if ($dumpSql) { $sqls = $schemaTool->getCreateSchemaSql($metadatas); foreach ($sqls as $sql) { $ui->writeln(sprintf('%s;', $sql)); } return 0; } $notificationUi = $ui->getErrorStyle(); $notificationUi->caution('This operation should not be executed in a production environment!'); $notificationUi->text('Creating database schema...'); $notificationUi->newLine(); $schemaTool->createSchema($metadatas); $notificationUi->success('Database schema created successfully!'); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/DropCommand.php 0000644 00000010455 15120025736 0021353 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function count; use function sprintf; /** * Command to drop the database schema for a set of classes based on their mappings. * * @link www.doctrine-project.org */ class DropCommand extends AbstractCommand { /** @return void */ protected function configure() { $this->setName('orm:schema-tool:drop') ->setDescription('Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Instead of trying to apply generated SQLs into EntityManager Storage Connection, output them.') ->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for the deletion of the database, but force the operation to run.") ->addOption('full-database', null, InputOption::VALUE_NONE, 'Instead of using the Class Metadata to detect the database table schema, drop ALL assets that the database contains.') ->setHelp(<<<'EOT' Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output. Beware that the complete database is dropped by this command, even tables that are not relevant to your metadata model. <comment>Hint:</comment> If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return !str_starts_with($assetName, 'audit_'); }); EOT ); } /** * {@inheritDoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui) { $isFullDatabaseDrop = $input->getOption('full-database'); $dumpSql = $input->getOption('dump-sql') === true; $force = $input->getOption('force') === true; if ($dumpSql) { if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } foreach ($sqls as $sql) { $ui->writeln(sprintf('%s;', $sql)); } return 0; } $notificationUi = $ui->getErrorStyle(); if ($force) { $notificationUi->text('Dropping database schema...'); $notificationUi->newLine(); if ($isFullDatabaseDrop) { $schemaTool->dropDatabase(); } else { $schemaTool->dropSchema($metadatas); } $notificationUi->success('Database schema dropped successfully!'); return 0; } $notificationUi->caution('This operation should not be executed in a production environment!'); if ($isFullDatabaseDrop) { $sqls = $schemaTool->getDropDatabaseSQL(); } else { $sqls = $schemaTool->getDropSchemaSQL($metadatas); } if (empty($sqls)) { $notificationUi->success('Nothing to drop. The database is empty!'); return 0; } $notificationUi->text( [ sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', count($sqls)), '', 'Please run the operation by passing one - or both - of the following options:', '', sprintf(' <info>%s --force</info> to execute the command', $this->getName()), sprintf(' <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()), ] ); return 1; } } orm/lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/UpdateCommand.php 0000644 00000013061 15120025736 0021665 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command\SchemaTool; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function count; use function sprintf; /** * Command to generate the SQL needed to update the database schema to match * the current mapping information. * * @link www.doctrine-project.org */ class UpdateCommand extends AbstractCommand { /** @var string */ protected $name = 'orm:schema-tool:update'; /** @return void */ protected function configure() { $this->setName($this->name) ->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('complete', null, InputOption::VALUE_NONE, 'If defined, all assets of the database which are not relevant to the current metadata will be dropped.') ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dumps the generated SQL statements to the screen (does not execute them).') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Causes the generated SQL statements to be physically executed against your database.') ->setHelp(<<<'EOT' The <info>%command.name%</info> command generates the SQL needed to synchronize the database schema with the current mapping metadata of the default entity manager. For example, if you add metadata for a new column to an entity, this command would generate and output the SQL needed to add the new column to the database: <info>%command.name% --dump-sql</info> Alternatively, you can execute the generated queries: <info>%command.name% --force</info> If both options are specified, the queries are output and then executed: <info>%command.name% --dump-sql --force</info> Finally, be aware that if the <info>--complete</info> option is passed, this task will drop all database assets (e.g. tables, etc) that are *not* described by the current metadata. In other words, without this option, this task leaves untouched any "extra" tables that exist in the database, but which aren't described by any metadata. Not passing that option is deprecated. <comment>Hint:</comment> If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return !str_starts_with($assetName, 'audit_'); }); EOT ); } /** * {@inheritDoc} */ protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui) { $notificationUi = $ui->getErrorStyle(); // Defining if update is complete or not (--complete not defined means $saveMode = true) $saveMode = ! $input->getOption('complete'); if ($saveMode) { $notificationUi->warning(sprintf( 'Not passing the "--complete" option to "%s" is deprecated and will not be supported when using doctrine/dbal 4', $this->getName() ?? $this->name )); } $sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode); if (empty($sqls)) { $notificationUi->success('Nothing to update - your database is already in sync with the current entity metadata.'); return 0; } $dumpSql = $input->getOption('dump-sql') === true; $force = $input->getOption('force') === true; if ($dumpSql) { foreach ($sqls as $sql) { $ui->writeln(sprintf('%s;', $sql)); } } if ($force) { if ($dumpSql) { $notificationUi->newLine(); } $notificationUi->text('Updating database schema...'); $notificationUi->newLine(); $schemaTool->updateSchema($metadatas, $saveMode); $pluralization = count($sqls) === 1 ? 'query was' : 'queries were'; $notificationUi->text(sprintf(' <info>%s</info> %s executed', count($sqls), $pluralization)); $notificationUi->success('Database schema updated successfully!'); } if ($dumpSql || $force) { return 0; } $notificationUi->caution( [ 'This operation should not be executed in a production environment!', '', 'Use the incremental update to detect changes during development and use', 'the SQL DDL provided to manually update your database in production.', ] ); $notificationUi->text( [ sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', count($sqls)), '', 'Please run the operation by passing one - or both - of the following options:', '', sprintf(' <info>%s --force</info> to execute the command', $this->getName()), sprintf(' <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()), ] ); return 1; } } orm/lib/Doctrine/ORM/Tools/Console/Command/AbstractEntityManagerCommand.php 0000644 00000003200 15120025736 0022632 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use function assert; abstract class AbstractEntityManagerCommand extends Command { /** @var EntityManagerProvider|null */ private $entityManagerProvider; public function __construct(?EntityManagerProvider $entityManagerProvider = null) { parent::__construct(); $this->entityManagerProvider = $entityManagerProvider; } final protected function getEntityManager(InputInterface $input): EntityManagerInterface { // This is a backwards compatibility required check for commands extending Doctrine ORM commands if (! $input->hasOption('em') || $this->entityManagerProvider === null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8327', 'Not passing EntityManagerProvider as a dependency to command class "%s" is deprecated', static::class ); $helper = $this->getHelper('em'); assert($helper instanceof EntityManagerHelper); return $helper->getEntityManager(); } return $input->getOption('em') === null ? $this->entityManagerProvider->getDefaultManager() : $this->entityManagerProvider->getManager($input->getOption('em')); } } orm/lib/Doctrine/ORM/Tools/Console/Command/ConvertDoctrine1SchemaCommand.php 0000644 00000015014 15120025736 0022717 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Tools\ConvertDoctrine1Schema; use Doctrine\ORM\Tools\EntityGenerator; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\ORM\Tools\Export\Driver\AnnotationExporter; use InvalidArgumentException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function array_merge; use function file_exists; use function is_readable; use function is_writable; use function realpath; use function sprintf; use const PHP_EOL; /** * Command to convert a Doctrine 1 schema to a Doctrine 2 mapping file. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class ConvertDoctrine1SchemaCommand extends Command { /** @var EntityGenerator|null */ private $entityGenerator = null; /** @var ClassMetadataExporter|null */ private $metadataExporter = null; /** @return EntityGenerator */ public function getEntityGenerator() { if ($this->entityGenerator === null) { $this->entityGenerator = new EntityGenerator(); } return $this->entityGenerator; } /** @return void */ public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->entityGenerator = $entityGenerator; } /** @return ClassMetadataExporter */ public function getMetadataExporter() { if ($this->metadataExporter === null) { $this->metadataExporter = new ClassMetadataExporter(); } return $this->metadataExporter; } /** @return void */ public function setMetadataExporter(ClassMetadataExporter $metadataExporter) { $this->metadataExporter = $metadataExporter; } /** @return void */ protected function configure() { $this->setName('orm:convert-d1-schema') ->setAliases(['orm:convert:d1-schema']) ->setDescription('Converts Doctrine 1.x schema into a Doctrine 2.x schema') ->addArgument('from-path', InputArgument::REQUIRED, 'The path of Doctrine 1.X schema information.') ->addArgument('to-type', InputArgument::REQUIRED, 'The destination Doctrine 2.X mapping type.') ->addArgument('dest-path', InputArgument::REQUIRED, 'The path to generate your Doctrine 2.X mapping information.') ->addOption('from', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Optional paths of Doctrine 1.X schema information.', []) ->addOption('extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.') ->addOption('num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4) ->setHelp('Converts Doctrine 1.x schema into a Doctrine 2.x schema.'); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = new SymfonyStyle($input, $output); $ui->getErrorStyle()->warning('Command ' . $this->getName() . ' is deprecated and will be removed in Doctrine ORM 3.0.'); // Process source directories $fromPaths = array_merge([$input->getArgument('from-path')], $input->getOption('from')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); $toType = $input->getArgument('to-type'); $extend = $input->getOption('extend'); $numSpaces = (int) $input->getOption('num-spaces'); $this->convertDoctrine1Schema($fromPaths, $destPath, $toType, $numSpaces, $extend, $output); return 0; } /** * @param mixed[] $fromPaths * @param string $destPath * @param string $toType * @param int $numSpaces * @param string|null $extend * * @return void * * @throws InvalidArgumentException */ public function convertDoctrine1Schema(array $fromPaths, $destPath, $toType, $numSpaces, $extend, OutputInterface $output) { foreach ($fromPaths as &$dirName) { $dirName = realpath($dirName); if (! file_exists($dirName)) { throw new InvalidArgumentException( sprintf("Doctrine 1.X schema directory '<info>%s</info>' does not exist.", $dirName) ); } if (! is_readable($dirName)) { throw new InvalidArgumentException( sprintf("Doctrine 1.X schema directory '<info>%s</info>' does not have read permissions.", $dirName) ); } } if (! file_exists($destPath)) { throw new InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '<info>%s</info>' does not exist.", $destPath) ); } if (! is_writable($destPath)) { throw new InvalidArgumentException( sprintf("Doctrine 2.X mapping destination directory '<info>%s</info>' does not have write permissions.", $destPath) ); } $cme = $this->getMetadataExporter(); $exporter = $cme->getExporter($toType, $destPath); if ($exporter instanceof AnnotationExporter) { $entityGenerator = $this->getEntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces($numSpaces); if ($extend !== null) { $entityGenerator->setClassToExtend($extend); } } $converter = new ConvertDoctrine1Schema($fromPaths); $metadata = $converter->getMetadata(); if ($metadata) { $output->writeln(''); foreach ($metadata as $class) { $output->writeln(sprintf('Processing entity "<info>%s</info>"', $class->name)); } $exporter->setMetadata($metadata); $exporter->export(); $output->writeln(PHP_EOL . sprintf( 'Converting Doctrine 1.X schema to "<info>%s</info>" mapping type in "<info>%s</info>"', $toType, $destPath )); } else { $output->writeln('No Metadata Classes to process.'); } } } orm/lib/Doctrine/ORM/Tools/Console/Command/ConvertMappingCommand.php 0000644 00000016302 15120025736 0021342 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\ORM\Mapping\Driver\DatabaseDriver; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Doctrine\ORM\Tools\EntityGenerator; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\ORM\Tools\Export\Driver\AbstractExporter; use Doctrine\ORM\Tools\Export\Driver\AnnotationExporter; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function file_exists; use function is_dir; use function is_writable; use function method_exists; use function mkdir; use function realpath; use function sprintf; use function strtolower; /** * Command to convert your mapping information between the various formats. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class ConvertMappingCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:convert-mapping') ->setAliases(['orm:convert:mapping']) ->setDescription('Convert mapping information between supported formats') ->addArgument('to-type', InputArgument::REQUIRED, 'The mapping type to be converted.') ->addArgument('dest-path', InputArgument::REQUIRED, 'The path to generate your entities classes.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.') ->addOption('from-database', null, null, 'Whether or not to convert mapping information from existing database.') ->addOption('extend', null, InputOption::VALUE_OPTIONAL, 'Defines a base class to be extended by generated entity classes.') ->addOption('num-spaces', null, InputOption::VALUE_OPTIONAL, 'Defines the number of indentation spaces', 4) ->addOption('namespace', null, InputOption::VALUE_OPTIONAL, 'Defines a namespace for the generated entity classes, if converted from database.') ->setHelp(<<<'EOT' Convert mapping information between supported formats. This is an execute <info>one-time</info> command. It should not be necessary for you to call this method multiple times, especially when using the <comment>--from-database</comment> flag. Converting an existing database schema into mapping files only solves about 70-80% of the necessary mapping information. Additionally the detection from an existing database cannot detect inverse associations, inheritance types, entities with foreign keys as primary keys and many of the semantical operations on associations such as cascade. <comment>Hint:</comment> There is no need to convert YAML or XML mapping files to annotations every time you make changes. All mapping drivers are first class citizens in Doctrine 2 and can be used as runtime mapping for the ORM. <comment>Hint:</comment> If you have a database with tables that should not be managed by the ORM, you can use a DBAL functionality to filter the tables and sequences down on a global level: $config->setSchemaAssetsFilter(function (string|AbstractAsset $assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return !str_starts_with($assetName, 'audit_'); }); EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = new SymfonyStyle($input, $output); $ui->getErrorStyle()->warning('Command ' . $this->getName() . ' is deprecated and will be removed in Doctrine ORM 3.0.'); $em = $this->getEntityManager($input); if ($input->getOption('from-database') === true) { $databaseDriver = new DatabaseDriver( method_exists(Connection::class, 'createSchemaManager') ? $em->getConnection()->createSchemaManager() : $em->getConnection()->getSchemaManager() ); $em->getConfiguration()->setMetadataDriverImpl( $databaseDriver ); $namespace = $input->getOption('namespace'); if ($namespace !== null) { $databaseDriver->setNamespace($namespace); } } $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); // Process destination directory $destPath = $input->getArgument('dest-path'); if (! is_dir($destPath)) { mkdir($destPath, 0775, true); } $destPath = realpath($destPath); if (! file_exists($destPath)) { throw new InvalidArgumentException( sprintf("Mapping destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path')) ); } if (! is_writable($destPath)) { throw new InvalidArgumentException( sprintf("Mapping destination directory '<info>%s</info>' does not have write permissions.", $destPath) ); } $toType = strtolower($input->getArgument('to-type')); $exporter = $this->getExporter($toType, $destPath); $exporter->setOverwriteExistingFiles($input->getOption('force')); if ($exporter instanceof AnnotationExporter) { $entityGenerator = new EntityGenerator(); $exporter->setEntityGenerator($entityGenerator); $entityGenerator->setNumSpaces((int) $input->getOption('num-spaces')); $extend = $input->getOption('extend'); if ($extend !== null) { $entityGenerator->setClassToExtend($extend); } } if (empty($metadata)) { $ui->success('No Metadata Classes to process.'); return 0; } foreach ($metadata as $class) { $ui->text(sprintf('Processing entity "<info>%s</info>"', $class->name)); } $exporter->setMetadata($metadata); $exporter->export(); $ui->newLine(); $ui->text( sprintf( 'Exporting "<info>%s</info>" mapping information to "<info>%s</info>"', $toType, $destPath ) ); return 0; } /** * @param string $toType * @param string $destPath * * @return AbstractExporter */ protected function getExporter($toType, $destPath) { $cme = new ClassMetadataExporter(); return $cme->getExporter($toType, $destPath); } } orm/lib/Doctrine/ORM/Tools/Console/Command/EnsureProductionSettingsCommand.php 0000644 00000003557 15120025736 0023447 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Throwable; /** * Command to ensure that Doctrine is properly configured for a production environment. * * @deprecated * * @link www.doctrine-project.org */ class EnsureProductionSettingsCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:ensure-production-settings') ->setDescription('Verify that Doctrine is properly configured for a production environment') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('complete', null, InputOption::VALUE_NONE, 'Flag to also inspect database connection existence.') ->setHelp('Verify that Doctrine is properly configured for a production environment.'); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $ui->warning('This console command has been deprecated and will be removed in a future version of Doctrine ORM.'); $em = $this->getEntityManager($input); try { $em->getConfiguration()->ensureProductionSettings(); if ($input->getOption('complete') === true) { $em->getConnection()->connect(); } } catch (Throwable $e) { $ui->error($e->getMessage()); return 1; } $ui->success('Environment is correctly configured for production.'); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/GenerateEntitiesCommand.php 0000644 00000014033 15120025736 0021644 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Doctrine\ORM\Tools\EntityGenerator; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function file_exists; use function is_writable; use function realpath; use function sprintf; /** * Command to generate entity classes and method stubs from your mapping information. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class GenerateEntitiesCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:generate-entities') ->setAliases(['orm:generate:entities']) ->setDescription('Generate entity classes and method stubs from your mapping information') ->addArgument('dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.') ->addOption('generate-annotations', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate annotation metadata on entities.', false) ->addOption('generate-methods', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should generate stub methods on entities.', true) ->addOption('regenerate-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should regenerate entity if it exists.', false) ->addOption('update-entities', null, InputOption::VALUE_OPTIONAL, 'Flag to define if generator should only update entity if it exists.', true) ->addOption('extend', null, InputOption::VALUE_REQUIRED, 'Defines a base class to be extended by generated entity classes.') ->addOption('num-spaces', null, InputOption::VALUE_REQUIRED, 'Defines the number of indentation spaces', 4) ->addOption('no-backup', null, InputOption::VALUE_NONE, 'Flag to define if generator should avoid backuping existing entity file if it exists.') ->setHelp(<<<'EOT' Generate entity classes and method stubs from your mapping information. If you use the <comment>--update-entities</comment> or <comment>--regenerate-entities</comment> flags your existing code gets overwritten. The EntityGenerator will only append new code to your file and will not delete the old code. However this approach may still be prone to error and we suggest you use code repositories such as GIT or SVN to make backups of your code. It makes sense to generate the entity code if you are using entities as Data Access Objects only and don't put much additional logic on them. If you are however putting much more logic on the entities you should refrain from using the entity-generator and code your entities manually. <error>Important:</error> Even if you specified Inheritance options in your XML or YAML Mapping files the generator cannot generate the base and child classes for you correctly, because it doesn't know which class is supposed to extend which. You have to adjust the entity code manually for inheritance to work! EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $ui->warning('Command ' . $this->getName() . ' is deprecated and will be removed in Doctrine ORM 3.0.'); $em = $this->getEntityManager($input); $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadatas = $cmf->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if (! file_exists($destPath)) { throw new InvalidArgumentException( sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path')) ); } if (! is_writable($destPath)) { throw new InvalidArgumentException( sprintf("Entities destination directory '<info>%s</info>' does not have write permissions.", $destPath) ); } if (empty($metadatas)) { $ui->success('No Metadata Classes to process.'); return 0; } $entityGenerator = new EntityGenerator(); $entityGenerator->setGenerateAnnotations($input->getOption('generate-annotations')); $entityGenerator->setGenerateStubMethods($input->getOption('generate-methods')); $entityGenerator->setRegenerateEntityIfExists($input->getOption('regenerate-entities')); $entityGenerator->setUpdateEntityIfExists($input->getOption('update-entities')); $entityGenerator->setNumSpaces((int) $input->getOption('num-spaces')); $entityGenerator->setBackupExisting(! $input->getOption('no-backup')); $extend = $input->getOption('extend'); if ($extend !== null) { $entityGenerator->setClassToExtend($extend); } foreach ($metadatas as $metadata) { $ui->text(sprintf('Processing entity "<info>%s</info>"', $metadata->name)); } // Generating Entities $entityGenerator->generate($metadatas, $destPath); // Outputting information message $ui->newLine(); $ui->success(sprintf('Entity classes generated to "%s"', $destPath)); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/GenerateProxiesCommand.php 0000644 00000006547 15120025736 0021524 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Tools\Console\MetadataFilter; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function file_exists; use function is_dir; use function is_writable; use function mkdir; use function realpath; use function sprintf; /** * Command to (re)generate the proxy classes used by doctrine. * * @link www.doctrine-project.org */ class GenerateProxiesCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:generate-proxies') ->setAliases(['orm:generate:proxies']) ->setDescription('Generates proxy classes for entity classes') ->addArgument('dest-path', InputArgument::OPTIONAL, 'The path to generate your proxy classes. If none is provided, it will attempt to grab from configuration.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.') ->setHelp('Generates proxy classes for entity classes.'); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); // Process destination directory $destPath = $input->getArgument('dest-path'); if ($destPath === null) { $destPath = $em->getConfiguration()->getProxyDir(); if ($destPath === null) { throw new InvalidArgumentException('Proxy directory cannot be null'); } } if (! is_dir($destPath)) { mkdir($destPath, 0775, true); } $destPath = realpath($destPath); if (! file_exists($destPath)) { throw new InvalidArgumentException( sprintf("Proxies destination directory '<info>%s</info>' does not exist.", $em->getConfiguration()->getProxyDir()) ); } if (! is_writable($destPath)) { throw new InvalidArgumentException( sprintf("Proxies destination directory '<info>%s</info>' does not have write permissions.", $destPath) ); } if (empty($metadatas)) { $ui->success('No Metadata Classes to process.'); return 0; } foreach ($metadatas as $metadata) { $ui->text(sprintf('Processing entity "<info>%s</info>"', $metadata->name)); } // Generating Proxies $em->getProxyFactory()->generateProxyClasses($metadatas, $destPath); // Outputting information message $ui->newLine(); $ui->text(sprintf('Proxy classes generated to "<info>%s</info>"', $destPath)); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/GenerateRepositoriesCommand.php 0000644 00000007320 15120025736 0022550 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\EntityRepositoryGenerator; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function file_exists; use function is_writable; use function realpath; use function sprintf; /** * Command to generate repository classes for mapping information. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class GenerateRepositoriesCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:generate-repositories') ->setAliases(['orm:generate:repositories']) ->setDescription('Generate repository classes from your mapping information') ->addArgument('dest-path', InputArgument::REQUIRED, 'The path to generate your repository classes.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be processed.') ->setHelp('Generate repository classes from your mapping information.'); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $ui->warning('Command ' . $this->getName() . ' is deprecated and will be removed in Doctrine ORM 3.0.'); $em = $this->getEntityManager($input); $metadatas = $em->getMetadataFactory()->getAllMetadata(); $metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter')); $repositoryName = $em->getConfiguration()->getDefaultRepositoryClassName(); // Process destination directory $destPath = realpath($input->getArgument('dest-path')); if (! file_exists($destPath)) { throw new InvalidArgumentException( sprintf("Entities destination directory '<info>%s</info>' does not exist.", $input->getArgument('dest-path')) ); } if (! is_writable($destPath)) { throw new InvalidArgumentException( sprintf("Entities destination directory '<info>%s</info>' does not have write permissions.", $destPath) ); } if (empty($metadatas)) { $ui->success('No Metadata Classes to process.'); return 0; } $numRepositories = 0; $generator = new EntityRepositoryGenerator(); $generator->setDefaultRepositoryName($repositoryName); foreach ($metadatas as $metadata) { if ($metadata->customRepositoryClassName) { $ui->text(sprintf('Processing repository "<info>%s</info>"', $metadata->customRepositoryClassName)); $generator->writeEntityRepositoryClass($metadata->customRepositoryClassName, $destPath); ++$numRepositories; } } if ($numRepositories === 0) { $ui->text('No Repository classes were found to be processed.'); return 0; } // Outputting information message $ui->newLine(); $ui->text(sprintf('Repository classes generated to "<info>%s</info>"', $destPath)); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/InfoCommand.php 0000644 00000005140 15120025736 0017277 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function count; use function sprintf; /** * Show information about mapped entities. * * @link www.doctrine-project.org */ class InfoCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:info') ->setDescription('Show basic information about all mapped entities') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->setHelp(<<<'EOT' The <info>%command.name%</info> shows basic information about which entities exist and possibly if their mapping information contains errors or not. EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $entityManager = $this->getEntityManager($input); $entityClassNames = $entityManager->getConfiguration() ->getMetadataDriverImpl() ->getAllClassNames(); if (! $entityClassNames) { $ui->caution( [ 'You do not have any mapped Doctrine ORM entities according to the current configuration.', 'If you have entities or mapping files you should check your mapping configuration for errors.', ] ); return 1; } $ui->text(sprintf('Found <info>%d</info> mapped entities:', count($entityClassNames))); $ui->newLine(); $failure = false; foreach ($entityClassNames as $entityClassName) { try { $entityManager->getClassMetadata($entityClassName); $ui->text(sprintf('<info>[OK]</info> %s', $entityClassName)); } catch (MappingException $e) { $ui->text( [ sprintf('<error>[FAIL]</error> %s', $entityClassName), sprintf('<comment>%s</comment>', $e->getMessage()), '', ] ); $failure = true; } } return $failure ? 1 : 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/MappingDescribeCommand.php 0000644 00000023547 15120025736 0021453 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\MappingException; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function array_filter; use function array_map; use function array_merge; use function count; use function current; use function get_debug_type; use function implode; use function is_array; use function is_bool; use function is_object; use function is_scalar; use function json_encode; use function preg_match; use function preg_quote; use function print_r; use function sprintf; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; /** * Show information about mapped entities. * * @link www.doctrine-project.org * * @psalm-import-type AssociationMapping from ClassMetadata * @psalm-import-type FieldMapping from ClassMetadata */ final class MappingDescribeCommand extends AbstractEntityManagerCommand { protected function configure(): void { $this->setName('orm:mapping:describe') ->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity') ->setDescription('Display information about mapped objects') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->setHelp(<<<'EOT' The %command.full_name% command describes the metadata for the given full or partial entity class name. <info>%command.full_name%</info> My\Namespace\Entity\MyEntity Or: <info>%command.full_name%</info> MyEntity EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $entityManager = $this->getEntityManager($input); $this->displayEntity($input->getArgument('entityName'), $entityManager, $ui); return 0; } /** * Display all the mapping information for a single Entity. * * @param string $entityName Full or partial entity class name */ private function displayEntity( string $entityName, EntityManagerInterface $entityManager, SymfonyStyle $ui ): void { $metadata = $this->getClassMetadata($entityName, $entityManager); $ui->table( ['Field', 'Value'], array_merge( [ $this->formatField('Name', $metadata->name), $this->formatField('Root entity name', $metadata->rootEntityName), $this->formatField('Custom generator definition', $metadata->customGeneratorDefinition), $this->formatField('Custom repository class', $metadata->customRepositoryClassName), $this->formatField('Mapped super class?', $metadata->isMappedSuperclass), $this->formatField('Embedded class?', $metadata->isEmbeddedClass), $this->formatField('Parent classes', $metadata->parentClasses), $this->formatField('Sub classes', $metadata->subClasses), $this->formatField('Embedded classes', $metadata->subClasses), $this->formatField('Named queries', $metadata->namedQueries), $this->formatField('Named native queries', $metadata->namedNativeQueries), $this->formatField('SQL result set mappings', $metadata->sqlResultSetMappings), $this->formatField('Identifier', $metadata->identifier), $this->formatField('Inheritance type', $metadata->inheritanceType), $this->formatField('Discriminator column', $metadata->discriminatorColumn), $this->formatField('Discriminator value', $metadata->discriminatorValue), $this->formatField('Discriminator map', $metadata->discriminatorMap), $this->formatField('Generator type', $metadata->generatorType), $this->formatField('Table', $metadata->table), $this->formatField('Composite identifier?', $metadata->isIdentifierComposite), $this->formatField('Foreign identifier?', $metadata->containsForeignIdentifier), $this->formatField('Enum identifier?', $metadata->containsEnumIdentifier), $this->formatField('Sequence generator definition', $metadata->sequenceGeneratorDefinition), $this->formatField('Change tracking policy', $metadata->changeTrackingPolicy), $this->formatField('Versioned?', $metadata->isVersioned), $this->formatField('Version field', $metadata->versionField), $this->formatField('Read only?', $metadata->isReadOnly), $this->formatEntityListeners($metadata->entityListeners), ], [$this->formatField('Association mappings:', '')], $this->formatMappings($metadata->associationMappings), [$this->formatField('Field mappings:', '')], $this->formatMappings($metadata->fieldMappings) ) ); } /** * Return all mapped entity class names * * @return string[] * @psalm-return class-string[] */ private function getMappedEntities(EntityManagerInterface $entityManager): array { $entityClassNames = $entityManager->getConfiguration() ->getMetadataDriverImpl() ->getAllClassNames(); if (! $entityClassNames) { throw new InvalidArgumentException( 'You do not have any mapped Doctrine ORM entities according to the current configuration. ' . 'If you have entities or mapping files you should check your mapping configuration for errors.' ); } return $entityClassNames; } /** * Return the class metadata for the given entity * name * * @param string $entityName Full or partial entity name */ private function getClassMetadata( string $entityName, EntityManagerInterface $entityManager ): ClassMetadata { try { return $entityManager->getClassMetadata($entityName); } catch (MappingException $e) { } $matches = array_filter( $this->getMappedEntities($entityManager), static function ($mappedEntity) use ($entityName) { return preg_match('{' . preg_quote($entityName) . '}', $mappedEntity); } ); if (! $matches) { throw new InvalidArgumentException(sprintf( 'Could not find any mapped Entity classes matching "%s"', $entityName )); } if (count($matches) > 1) { throw new InvalidArgumentException(sprintf( 'Entity name "%s" is ambiguous, possible matches: "%s"', $entityName, implode(', ', $matches) )); } return $entityManager->getClassMetadata(current($matches)); } /** * Format the given value for console output * * @param mixed $value */ private function formatValue($value): string { if ($value === '') { return ''; } if ($value === null) { return '<comment>Null</comment>'; } if (is_bool($value)) { return '<comment>' . ($value ? 'True' : 'False') . '</comment>'; } if (empty($value)) { return '<comment>Empty</comment>'; } if (is_array($value)) { return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); } if (is_object($value)) { return sprintf('<%s>', get_debug_type($value)); } if (is_scalar($value)) { return (string) $value; } throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true))); } /** * Add the given label and value to the two column table output * * @param string $label Label for the value * @param mixed $value A Value to show * * @return string[] * @psalm-return array{0: string, 1: string} */ private function formatField(string $label, $value): array { if ($value === null) { $value = '<comment>None</comment>'; } return [sprintf('<info>%s</info>', $label), $this->formatValue($value)]; } /** * Format the association mappings * * @psalm-param array<string, FieldMapping|AssociationMapping> $propertyMappings * * @return string[][] * @psalm-return list<array{0: string, 1: string}> */ private function formatMappings(array $propertyMappings): array { $output = []; foreach ($propertyMappings as $propertyName => $mapping) { $output[] = $this->formatField(sprintf(' %s', $propertyName), ''); foreach ($mapping as $field => $value) { $output[] = $this->formatField(sprintf(' %s', $field), $this->formatValue($value)); } } return $output; } /** * Format the entity listeners * * @psalm-param list<object> $entityListeners * * @return string[] * @psalm-return array{0: string, 1: string} */ private function formatEntityListeners(array $entityListeners): array { return $this->formatField('Entity listeners', array_map('get_class', $entityListeners)); } } orm/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php 0000644 00000010475 15120025736 0017620 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\Common\Util\Debug; use LogicException; use RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function constant; use function defined; use function is_numeric; use function sprintf; use function str_replace; use function strtoupper; /** * Command to execute DQL queries in a given EntityManager. * * @link www.doctrine-project.org */ class RunDqlCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:run-dql') ->setDescription('Executes arbitrary DQL directly from the command line') ->addArgument('dql', InputArgument::REQUIRED, 'The DQL to execute.') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('hydrate', null, InputOption::VALUE_REQUIRED, 'Hydration mode of result set. Should be either: object, array, scalar or single-scalar.', 'object') ->addOption('first-result', null, InputOption::VALUE_REQUIRED, 'The first result in the result set.') ->addOption('max-result', null, InputOption::VALUE_REQUIRED, 'The maximum number of results in the result set.') ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7) ->addOption('show-sql', null, InputOption::VALUE_NONE, 'Dump generated SQL instead of executing query') ->setHelp(<<<'EOT' The <info>%command.name%</info> command executes the given DQL query and outputs the results: <info>php %command.full_name% "SELECT u FROM App\Entity\User u"</info> You can also optionally specify some additional options like what type of hydration to use when executing the query: <info>php %command.full_name% "SELECT u FROM App\Entity\User u" --hydrate=array</info> Additionally you can specify the first result and maximum amount of results to show: <info>php %command.full_name% "SELECT u FROM App\Entity\User u" --first-result=0 --max-result=30</info> EOT ); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = new SymfonyStyle($input, $output); $em = $this->getEntityManager($input); $dql = $input->getArgument('dql'); if ($dql === null) { throw new RuntimeException("Argument 'dql' is required in order to execute this command correctly."); } $depth = $input->getOption('depth'); if (! is_numeric($depth)) { throw new LogicException("Option 'depth' must contain an integer value"); } $hydrationModeName = (string) $input->getOption('hydrate'); $hydrationMode = 'Doctrine\ORM\Query::HYDRATE_' . strtoupper(str_replace('-', '_', $hydrationModeName)); if (! defined($hydrationMode)) { throw new RuntimeException(sprintf( "Hydration mode '%s' does not exist. It should be either: object. array, scalar or single-scalar.", $hydrationModeName )); } $query = $em->createQuery($dql); $firstResult = $input->getOption('first-result'); if ($firstResult !== null) { if (! is_numeric($firstResult)) { throw new LogicException("Option 'first-result' must contain an integer value"); } $query->setFirstResult((int) $firstResult); } $maxResult = $input->getOption('max-result'); if ($maxResult !== null) { if (! is_numeric($maxResult)) { throw new LogicException("Option 'max-result' must contain an integer value"); } $query->setMaxResults((int) $maxResult); } if ($input->getOption('show-sql')) { $ui->text($query->getSQL()); return 0; } $resultSet = $query->execute([], constant($hydrationMode)); $ui->text(Debug::dump($resultSet, (int) $input->getOption('depth'), true, false)); return 0; } } orm/lib/Doctrine/ORM/Tools/Console/Command/ValidateSchemaCommand.php 0000644 00000006236 15120025736 0021265 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Command; use Doctrine\ORM\Tools\SchemaValidator; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function count; use function sprintf; /** * Command to validate that the current mapping is valid. * * @link www.doctrine-project.com */ class ValidateSchemaCommand extends AbstractEntityManagerCommand { /** @return void */ protected function configure() { $this->setName('orm:validate-schema') ->setDescription('Validate the mapping files') ->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on') ->addOption('skip-mapping', null, InputOption::VALUE_NONE, 'Skip the mapping validation check') ->addOption('skip-sync', null, InputOption::VALUE_NONE, 'Skip checking if the mapping is in sync with the database') ->setHelp('Validate that the mapping files are correct and in sync with the database.'); } /** * {@inheritDoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); $em = $this->getEntityManager($input); $validator = new SchemaValidator($em); $exit = 0; $ui->section('Mapping'); if ($input->getOption('skip-mapping')) { $ui->text('<comment>[SKIPPED] The mapping was not checked.</comment>'); } else { $errors = $validator->validateMapping(); if ($errors) { foreach ($errors as $className => $errorMessages) { $ui->text( sprintf( '<error>[FAIL]</error> The entity-class <comment>%s</comment> mapping is invalid:', $className ) ); $ui->listing($errorMessages); $ui->newLine(); } ++$exit; } else { $ui->success('The mapping files are correct.'); } } $ui->section('Database'); if ($input->getOption('skip-sync')) { $ui->text('<comment>[SKIPPED] The database was not checked for synchronicity.</comment>'); } elseif (! $validator->schemaInSyncWithMetadata()) { $ui->error('The database schema is not in sync with the current mapping file.'); if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $sqls = $validator->getUpdateSchemaList(); $ui->comment(sprintf('<info>%d</info> schema diff(s) detected:', count($sqls))); foreach ($sqls as $sql) { $ui->text(sprintf(' %s;', $sql)); } } $exit += 2; } else { $ui->success('The database schema is in sync with the mapping files.'); } return $exit; } } orm/lib/Doctrine/ORM/Tools/Console/EntityManagerProvider/ConnectionFromManagerProvider.php 0000644 00000001510 15120025736 0025737 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\ORM\Tools\Console\EntityManagerProvider; final class ConnectionFromManagerProvider implements ConnectionProvider { /** @var EntityManagerProvider */ private $entityManagerProvider; public function __construct(EntityManagerProvider $entityManagerProvider) { $this->entityManagerProvider = $entityManagerProvider; } public function getDefaultConnection(): Connection { return $this->entityManagerProvider->getDefaultManager()->getConnection(); } public function getConnection(string $name): Connection { return $this->entityManagerProvider->getManager($name)->getConnection(); } } orm/lib/Doctrine/ORM/Tools/Console/EntityManagerProvider/HelperSetManagerProvider.php 0000644 00000002561 15120025736 0024716 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use Symfony\Component\Console\Helper\HelperSet; use function assert; /** @deprecated This class will be removed in ORM 3.0 without replacement. */ final class HelperSetManagerProvider implements EntityManagerProvider { /** @var HelperSet */ private $helperSet; public function __construct(HelperSet $helperSet) { $this->helperSet = $helperSet; Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8327', 'Use of a HelperSet and the HelperSetManagerProvider is deprecated and will be removed in ORM 3.0' ); } public function getManager(string $name): EntityManagerInterface { if ($name !== 'default') { throw UnknownManagerException::unknownManager($name, ['default']); } return $this->getDefaultManager(); } public function getDefaultManager(): EntityManagerInterface { $helper = $this->helperSet->get('entityManager'); assert($helper instanceof EntityManagerHelper); return $helper->getEntityManager(); } } orm/lib/Doctrine/ORM/Tools/Console/EntityManagerProvider/SingleManagerProvider.php 0000644 00000001760 15120025736 0024244 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider; final class SingleManagerProvider implements EntityManagerProvider { /** @var EntityManagerInterface */ private $entityManager; /** @var string */ private $defaultManagerName; public function __construct(EntityManagerInterface $entityManager, string $defaultManagerName = 'default') { $this->entityManager = $entityManager; $this->defaultManagerName = $defaultManagerName; } public function getDefaultManager(): EntityManagerInterface { return $this->entityManager; } public function getManager(string $name): EntityManagerInterface { if ($name !== $this->defaultManagerName) { throw UnknownManagerException::unknownManager($name, [$this->defaultManagerName]); } return $this->entityManager; } } orm/lib/Doctrine/ORM/Tools/Console/EntityManagerProvider/UnknownManagerException.php 0000644 00000001112 15120025736 0024615 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\EntityManagerProvider; use OutOfBoundsException; use function implode; use function sprintf; final class UnknownManagerException extends OutOfBoundsException { /** @psalm-param list<string> $knownManagers */ public static function unknownManager(string $unknownManager, array $knownManagers = []): self { return new self(sprintf( 'Requested unknown entity manager: %s, known managers: %s', $unknownManager, implode(', ', $knownManagers) )); } } orm/lib/Doctrine/ORM/Tools/Console/Helper/EntityManagerHelper.php 0000644 00000002210 15120025736 0020650 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console\Helper; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Helper\Helper; /** * Doctrine CLI Connection Helper. * * @deprecated This class will be removed in ORM 3.0 without replacement. */ class EntityManagerHelper extends Helper { /** * Doctrine ORM EntityManagerInterface. * * @var EntityManagerInterface */ protected $_em; public function __construct(EntityManagerInterface $em) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9641', 'The %s class is deprecated and will be removed in ORM 3.0', self::class ); $this->_em = $em; } /** * Retrieves Doctrine ORM EntityManager. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->_em; } /** * {@inheritDoc} * * @return string */ public function getName() { return 'entityManager'; } } orm/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php 0000644 00000012655 15120025736 0016334 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console; use Composer\InstalledVersions; use Doctrine\DBAL\Tools\Console as DBALConsole; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider\ConnectionFromManagerProvider; use Doctrine\ORM\Tools\Console\EntityManagerProvider\HelperSetManagerProvider; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use OutOfBoundsException; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Helper\HelperSet; use function assert; use function class_exists; /** * Handles running the Console Tools inside Symfony Console context. */ final class ConsoleRunner { /** * Create a Symfony Console HelperSet * * @deprecated This method will be removed in ORM 3.0 without replacement. */ public static function createHelperSet(EntityManagerInterface $entityManager): HelperSet { $helpers = ['em' => new EntityManagerHelper($entityManager)]; if (class_exists(DBALConsole\Helper\ConnectionHelper::class)) { $helpers['db'] = new DBALConsole\Helper\ConnectionHelper($entityManager->getConnection()); } return new HelperSet($helpers); } /** * Runs console with the given helper set. * * @param HelperSet|EntityManagerProvider $helperSetOrProvider * @param SymfonyCommand[] $commands */ public static function run($helperSetOrProvider, array $commands = []): void { $cli = self::createApplication($helperSetOrProvider, $commands); $cli->run(); } /** * Creates a console application with the given helperset and * optional commands. * * @param HelperSet|EntityManagerProvider $helperSetOrProvider * @param SymfonyCommand[] $commands * * @throws OutOfBoundsException */ public static function createApplication($helperSetOrProvider, array $commands = []): Application { $version = InstalledVersions::getVersion('doctrine/orm'); assert($version !== null); $cli = new Application('Doctrine Command Line Interface', $version); $cli->setCatchExceptions(true); if ($helperSetOrProvider instanceof HelperSet) { $cli->setHelperSet($helperSetOrProvider); $helperSetOrProvider = new HelperSetManagerProvider($helperSetOrProvider); } self::addCommands($cli, $helperSetOrProvider); $cli->addCommands($commands); return $cli; } public static function addCommands(Application $cli, ?EntityManagerProvider $entityManagerProvider = null): void { if ($entityManagerProvider === null) { $entityManagerProvider = new HelperSetManagerProvider($cli->getHelperSet()); } $connectionProvider = new ConnectionFromManagerProvider($entityManagerProvider); if (class_exists(DBALConsole\Command\ImportCommand::class)) { $cli->add(new DBALConsole\Command\ImportCommand()); } $cli->addCommands( [ // DBAL Commands new DBALConsole\Command\ReservedWordsCommand($connectionProvider), new DBALConsole\Command\RunSqlCommand($connectionProvider), // ORM Commands new Command\ClearCache\CollectionRegionCommand($entityManagerProvider), new Command\ClearCache\EntityRegionCommand($entityManagerProvider), new Command\ClearCache\MetadataCommand($entityManagerProvider), new Command\ClearCache\QueryCommand($entityManagerProvider), new Command\ClearCache\QueryRegionCommand($entityManagerProvider), new Command\ClearCache\ResultCommand($entityManagerProvider), new Command\SchemaTool\CreateCommand($entityManagerProvider), new Command\SchemaTool\UpdateCommand($entityManagerProvider), new Command\SchemaTool\DropCommand($entityManagerProvider), new Command\EnsureProductionSettingsCommand($entityManagerProvider), new Command\ConvertDoctrine1SchemaCommand(), new Command\GenerateRepositoriesCommand($entityManagerProvider), new Command\GenerateEntitiesCommand($entityManagerProvider), new Command\GenerateProxiesCommand($entityManagerProvider), new Command\ConvertMappingCommand($entityManagerProvider), new Command\RunDqlCommand($entityManagerProvider), new Command\ValidateSchemaCommand($entityManagerProvider), new Command\InfoCommand($entityManagerProvider), new Command\MappingDescribeCommand($entityManagerProvider), ] ); } /** @deprecated This method will be removed in ORM 3.0 without replacement. */ public static function printCliConfigTemplate(): void { echo <<<'HELP' You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine Console working. You can use the following sample as a template: <?php use Doctrine\ORM\Tools\Console\ConsoleRunner; // replace with file to your own project bootstrap require_once 'bootstrap.php'; // replace with mechanism to retrieve EntityManager in your app $entityManager = GetEntityManager(); return ConsoleRunner::createHelperSet($entityManager); HELP; } } orm/lib/Doctrine/ORM/Tools/Console/EntityManagerProvider.php 0000644 00000000436 15120025736 0020014 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console; use Doctrine\ORM\EntityManagerInterface; interface EntityManagerProvider { public function getDefaultManager(): EntityManagerInterface; public function getManager(string $name): EntityManagerInterface; } orm/lib/Doctrine/ORM/Tools/Console/MetadataFilter.php 0000644 00000004363 15120025736 0016423 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Console; use ArrayIterator; use Countable; use Doctrine\Persistence\Mapping\ClassMetadata; use FilterIterator; use ReturnTypeWillChange; use RuntimeException; use function assert; use function count; use function iterator_to_array; use function preg_match; use function sprintf; /** * Used by CLI Tools to restrict entity-based commands to given patterns. * * @link www.doctrine-project.com */ class MetadataFilter extends FilterIterator implements Countable { /** @var mixed[] */ private $filter = []; /** * Filter Metadatas by one or more filter options. * * @param ClassMetadata[] $metadatas * @param string[]|string $filter * * @return ClassMetadata[] */ public static function filter(array $metadatas, $filter) { $metadatas = new MetadataFilter(new ArrayIterator($metadatas), $filter); return iterator_to_array($metadatas); } /** @param mixed[]|string $filter */ public function __construct(ArrayIterator $metadata, $filter) { $this->filter = (array) $filter; parent::__construct($metadata); } /** @return bool */ #[ReturnTypeWillChange] public function accept() { if (count($this->filter) === 0) { return true; } $it = $this->getInnerIterator(); $metadata = $it->current(); foreach ($this->filter as $filter) { $pregResult = preg_match('/' . $filter . '/', $metadata->getName()); if ($pregResult === false) { throw new RuntimeException( sprintf("Error while evaluating regex '/%s/'.", $filter) ); } if ($pregResult) { return true; } } return false; } /** @return ArrayIterator<int, ClassMetadata> */ #[ReturnTypeWillChange] public function getInnerIterator() { $innerIterator = parent::getInnerIterator(); assert($innerIterator instanceof ArrayIterator); return $innerIterator; } /** @return int */ #[ReturnTypeWillChange] public function count() { return count($this->getInnerIterator()); } } orm/lib/Doctrine/ORM/Tools/Event/GenerateSchemaEventArgs.php 0000644 00000001456 15120025736 0017706 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Schema\Schema; use Doctrine\ORM\EntityManagerInterface; /** * Event Args used for the Events::postGenerateSchema event. * * @link www.doctrine-project.com */ class GenerateSchemaEventArgs extends EventArgs { /** @var EntityManagerInterface */ private $em; /** @var Schema */ private $schema; public function __construct(EntityManagerInterface $em, Schema $schema) { $this->em = $em; $this->schema = $schema; } /** @return EntityManagerInterface */ public function getEntityManager() { return $this->em; } /** @return Schema */ public function getSchema() { return $this->schema; } } orm/lib/Doctrine/ORM/Tools/Event/GenerateSchemaTableEventArgs.php 0000644 00000002121 15120025736 0020644 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Doctrine\ORM\Mapping\ClassMetadata; /** * Event Args used for the Events::postGenerateSchemaTable event. * * @link www.doctrine-project.com */ class GenerateSchemaTableEventArgs extends EventArgs { /** @var ClassMetadata */ private $classMetadata; /** @var Schema */ private $schema; /** @var Table */ private $classTable; public function __construct(ClassMetadata $classMetadata, Schema $schema, Table $classTable) { $this->classMetadata = $classMetadata; $this->schema = $schema; $this->classTable = $classTable; } /** @return ClassMetadata */ public function getClassMetadata() { return $this->classMetadata; } /** @return Schema */ public function getSchema() { return $this->schema; } /** @return Table */ public function getClassTable() { return $this->classTable; } } orm/lib/Doctrine/ORM/Tools/Exception/MissingColumnException.php 0000644 00000001004 15120025736 0020524 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Exception; use Doctrine\ORM\Exception\ORMException; use function sprintf; final class MissingColumnException extends ORMException { public static function fromColumnSourceAndTarget(string $column, string $source, string $target): self { return new self(sprintf( 'Column name "%s" referenced for relation from %s towards %s does not exist.', $column, $source, $target )); } } orm/lib/Doctrine/ORM/Tools/Exception/NotSupported.php 0000644 00000000601 15120025736 0016526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Exception; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\SchemaToolException; final class NotSupported extends ORMException implements SchemaToolException { public static function create(): self { return new self('This behaviour is (currently) not supported by Doctrine 2'); } } orm/lib/Doctrine/ORM/Tools/Export/Driver/AbstractExporter.php 0000644 00000015062 15120025736 0020141 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\Export\ExportException; use function chmod; use function dirname; use function file_exists; use function file_put_contents; use function is_dir; use function mkdir; use function str_replace; /** * Abstract base class which is to be used for the Exporter drivers * which can be found in \Doctrine\ORM\Tools\Export\Driver. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ abstract class AbstractExporter { /** @var ClassMetadata[] */ protected $_metadata = []; /** @var string|null */ protected $_outputDir; /** @var string|null */ protected $_extension; /** @var bool */ protected $_overwriteExistingFiles = false; /** @param string|null $dir */ public function __construct($dir = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8458', '%s is deprecated with no replacement', self::class ); $this->_outputDir = $dir; } /** * @param bool $overwrite * * @return void */ public function setOverwriteExistingFiles($overwrite) { $this->_overwriteExistingFiles = $overwrite; } /** * Converts a single ClassMetadata instance to the exported format * and returns it. * * @return string */ abstract public function exportClassMetadata(ClassMetadataInfo $metadata); /** * Sets the array of ClassMetadata instances to export. * * @psalm-param list<ClassMetadata> $metadata * * @return void */ public function setMetadata(array $metadata) { $this->_metadata = $metadata; } /** * Gets the extension used to generated the path to a class. * * @return string|null */ public function getExtension() { return $this->_extension; } /** * Sets the directory to output the mapping files to. * * [php] * $exporter = new YamlExporter($metadata); * $exporter->setOutputDir(__DIR__ . '/yaml'); * $exporter->export(); * * @param string $dir * * @return void */ public function setOutputDir($dir) { $this->_outputDir = $dir; } /** * Exports each ClassMetadata instance to a single Doctrine Mapping file * named after the entity. * * @return void * * @throws ExportException */ public function export() { if (! is_dir($this->_outputDir)) { mkdir($this->_outputDir, 0775, true); } foreach ($this->_metadata as $metadata) { // In case output is returned, write it to a file, skip otherwise $output = $this->exportClassMetadata($metadata); if ($output) { $path = $this->_generateOutputPath($metadata); $dir = dirname($path); if (! is_dir($dir)) { mkdir($dir, 0775, true); } if (file_exists($path) && ! $this->_overwriteExistingFiles) { throw ExportException::attemptOverwriteExistingFile($path); } file_put_contents($path, $output); chmod($path, 0664); } } } /** * Generates the path to write the class for the given ClassMetadataInfo instance. * * @return string */ protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '.', $metadata->name) . $this->_extension; } /** * Sets the directory to output the mapping files to. * * [php] * $exporter = new YamlExporter($metadata, __DIR__ . '/yaml'); * $exporter->setExtension('.yml'); * $exporter->export(); * * @param string $extension * * @return void */ public function setExtension($extension) { $this->_extension = $extension; } /** * @param int $type * @psalm-param ClassMetadataInfo::INHERITANCE_TYPE_* $type * * @return string */ protected function _getInheritanceTypeString($type) { switch ($type) { case ClassMetadataInfo::INHERITANCE_TYPE_NONE: return 'NONE'; case ClassMetadataInfo::INHERITANCE_TYPE_JOINED: return 'JOINED'; case ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE: return 'SINGLE_TABLE'; case ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS: return 'PER_CLASS'; } } /** * @param int $mode * @psalm-param ClassMetadataInfo::FETCH_* $mode * * @return string */ protected function _getFetchModeString($mode) { switch ($mode) { case ClassMetadataInfo::FETCH_EAGER: return 'EAGER'; case ClassMetadataInfo::FETCH_EXTRA_LAZY: return 'EXTRA_LAZY'; case ClassMetadataInfo::FETCH_LAZY: return 'LAZY'; } } /** * @param int $policy * @psalm-param ClassMetadataInfo::CHANGETRACKING_* $policy * * @return string */ protected function _getChangeTrackingPolicyString($policy) { switch ($policy) { case ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT: return 'DEFERRED_IMPLICIT'; case ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT: return 'DEFERRED_EXPLICIT'; case ClassMetadataInfo::CHANGETRACKING_NOTIFY: return 'NOTIFY'; } } /** * @param int $type * @psalm-param ClassMetadataInfo::GENERATOR_TYPE_* $type * * @return string */ protected function _getIdGeneratorTypeString($type) { switch ($type) { case ClassMetadataInfo::GENERATOR_TYPE_AUTO: return 'AUTO'; case ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE: return 'SEQUENCE'; case ClassMetadataInfo::GENERATOR_TYPE_IDENTITY: return 'IDENTITY'; case ClassMetadataInfo::GENERATOR_TYPE_UUID: return 'UUID'; case ClassMetadataInfo::GENERATOR_TYPE_CUSTOM: return 'CUSTOM'; } } } orm/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php 0000644 00000003121 15120025736 0020501 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\EntityGenerator; use RuntimeException; use function str_replace; /** * ClassMetadata exporter for PHP classes with annotations. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class AnnotationExporter extends AbstractExporter { /** @var string */ protected $_extension = '.php'; /** @var EntityGenerator|null */ private $entityGenerator; /** * {@inheritDoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { if (! $this->entityGenerator) { throw new RuntimeException('For the AnnotationExporter you must set an EntityGenerator instance with the setEntityGenerator() method.'); } $this->entityGenerator->setGenerateAnnotations(true); $this->entityGenerator->setGenerateStubMethods(false); $this->entityGenerator->setRegenerateEntityIfExists(false); $this->entityGenerator->setUpdateEntityIfExists(false); return $this->entityGenerator->generateEntityClass($metadata); } /** @return string */ protected function _generateOutputPath(ClassMetadataInfo $metadata) { return $this->_outputDir . '/' . str_replace('\\', '/', $metadata->name) . $this->_extension; } /** @return void */ public function setEntityGenerator(EntityGenerator $entityGenerator) { $this->entityGenerator = $entityGenerator; } } orm/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php 0000644 00000016434 15120025736 0017131 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use function array_merge; use function count; use function implode; use function sprintf; use function str_repeat; use function str_replace; use function ucfirst; use function var_export; use const PHP_EOL; /** * ClassMetadata exporter for PHP code. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class PhpExporter extends AbstractExporter { /** @var string */ protected $_extension = '.php'; /** * {@inheritDoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $lines = []; $lines[] = '<?php'; $lines[] = null; $lines[] = 'use Doctrine\ORM\Mapping\ClassMetadataInfo;'; $lines[] = null; if ($metadata->isMappedSuperclass) { $lines[] = '$metadata->isMappedSuperclass = true;'; } $lines[] = '$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_' . $this->_getInheritanceTypeString($metadata->inheritanceType) . ');'; if ($metadata->customRepositoryClassName) { $lines[] = "\$metadata->customRepositoryClassName = '" . $metadata->customRepositoryClassName . "';"; } if ($metadata->table) { $lines[] = '$metadata->setPrimaryTable(' . $this->_varExport($metadata->table) . ');'; } if ($metadata->discriminatorColumn) { $lines[] = '$metadata->setDiscriminatorColumn(' . $this->_varExport($metadata->discriminatorColumn) . ');'; } if ($metadata->discriminatorMap) { $lines[] = '$metadata->setDiscriminatorMap(' . $this->_varExport($metadata->discriminatorMap) . ');'; } if ($metadata->changeTrackingPolicy) { $lines[] = '$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_' . $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy) . ');'; } if ($metadata->lifecycleCallbacks) { foreach ($metadata->lifecycleCallbacks as $event => $callbacks) { foreach ($callbacks as $callback) { $lines[] = sprintf("\$metadata->addLifecycleCallback('%s', '%s');", $callback, $event); } } } $lines = array_merge($lines, $this->processEntityListeners($metadata)); foreach ($metadata->fieldMappings as $fieldMapping) { $lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');'; } if (! $metadata->isIdentifierComposite) { $generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType); if ($generatorType) { $lines[] = '$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_' . $generatorType . ');'; } } foreach ($metadata->associationMappings as $associationMapping) { $cascade = ['remove', 'persist', 'refresh', 'merge', 'detach']; foreach ($cascade as $key => $value) { if (! $associationMapping['isCascade' . ucfirst($value)]) { unset($cascade[$key]); } } if (count($cascade) === 5) { $cascade = ['all']; } $method = null; $associationMappingArray = [ 'fieldName' => $associationMapping['fieldName'], 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ]; if (isset($associationMapping['fetch'])) { $associationMappingArray['fetch'] = $associationMapping['fetch']; } if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $method = 'mapOneToOne'; $oneToOneMappingArray = [ 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : [], 'orphanRemoval' => $associationMapping['orphanRemoval'], ]; $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); } elseif ($associationMapping['type'] === ClassMetadataInfo::ONE_TO_MANY) { $method = 'mapOneToMany'; $potentialAssociationMappingIndexes = [ 'mappedBy', 'orphanRemoval', 'orderBy', ]; $oneToManyMappingArray = []; foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $oneToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); } elseif ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_MANY) { $method = 'mapManyToMany'; $potentialAssociationMappingIndexes = [ 'mappedBy', 'joinTable', 'orderBy', ]; $manyToManyMappingArray = []; foreach ($potentialAssociationMappingIndexes as $index) { if (isset($associationMapping[$index])) { $manyToManyMappingArray[$index] = $associationMapping[$index]; } } $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); } $lines[] = '$metadata->' . $method . '(' . $this->_varExport($associationMappingArray) . ');'; } return implode("\n", $lines); } /** * @param mixed $var * * @return string */ protected function _varExport($var) { $export = var_export($var, true); $export = str_replace("\n", PHP_EOL . str_repeat(' ', 8), $export); $export = str_replace(' ', ' ', $export); $export = str_replace('array (', 'array(', $export); $export = str_replace('array( ', 'array(', $export); $export = str_replace(',)', ')', $export); $export = str_replace(', )', ')', $export); $export = str_replace(' ', ' ', $export); return $export; } /** * @return string[] * @psalm-return list<string> */ private function processEntityListeners(ClassMetadataInfo $metadata): array { $lines = []; foreach ($metadata->entityListeners as $event => $entityListenerConfig) { foreach ($entityListenerConfig as $entityListener) { $lines[] = sprintf( '$metadata->addEntityListener(%s, %s, %s);', var_export($event, true), var_export($entityListener['class'], true), var_export($entityListener['method'], true) ); } } return $lines; } } orm/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php 0000644 00000047307 15120025736 0017145 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use DOMDocument; use SimpleXMLElement; use function array_search; use function count; use function implode; use function is_array; use function strcmp; use function uasort; /** * ClassMetadata exporter for Doctrine XML mapping files. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class XmlExporter extends AbstractExporter { /** @var string */ protected $_extension = '.dcm.xml'; /** * {@inheritDoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><doctrine-mapping ' . 'xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" ' . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' . 'xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd" />'); if ($metadata->isMappedSuperclass) { $root = $xml->addChild('mapped-superclass'); } else { $root = $xml->addChild('entity'); } if ($metadata->customRepositoryClassName) { $root->addAttribute('repository-class', $metadata->customRepositoryClassName); } $root->addAttribute('name', $metadata->name); if (isset($metadata->table['name'])) { $root->addAttribute('table', $metadata->table['name']); } if (isset($metadata->table['schema'])) { $root->addAttribute('schema', $metadata->table['schema']); } if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $root->addAttribute('inheritance-type', $this->_getInheritanceTypeString($metadata->inheritanceType)); } if (isset($metadata->table['options'])) { $optionsXml = $root->addChild('options'); $this->exportTableOptions($optionsXml, $metadata->table['options']); } if ($metadata->discriminatorColumn) { $discriminatorColumnXml = $root->addChild('discriminator-column'); $discriminatorColumnXml->addAttribute('name', $metadata->discriminatorColumn['name']); $discriminatorColumnXml->addAttribute('type', $metadata->discriminatorColumn['type']); if (isset($metadata->discriminatorColumn['length'])) { $discriminatorColumnXml->addAttribute('length', (string) $metadata->discriminatorColumn['length']); } } if ($metadata->discriminatorMap) { $discriminatorMapXml = $root->addChild('discriminator-map'); foreach ($metadata->discriminatorMap as $value => $className) { $discriminatorMappingXml = $discriminatorMapXml->addChild('discriminator-mapping'); $discriminatorMappingXml->addAttribute('value', (string) $value); $discriminatorMappingXml->addAttribute('class', $className); } } $trackingPolicy = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); if ($trackingPolicy !== 'DEFERRED_IMPLICIT') { $root->addAttribute('change-tracking-policy', $trackingPolicy); } if (isset($metadata->table['indexes'])) { $indexesXml = $root->addChild('indexes'); foreach ($metadata->table['indexes'] as $name => $index) { $indexXml = $indexesXml->addChild('index'); $indexXml->addAttribute('name', $name); $indexXml->addAttribute('columns', implode(',', $index['columns'])); if (isset($index['flags'])) { $indexXml->addAttribute('flags', implode(',', $index['flags'])); } } } if (isset($metadata->table['uniqueConstraints'])) { $uniqueConstraintsXml = $root->addChild('unique-constraints'); foreach ($metadata->table['uniqueConstraints'] as $name => $unique) { $uniqueConstraintXml = $uniqueConstraintsXml->addChild('unique-constraint'); $uniqueConstraintXml->addAttribute('name', $name); $uniqueConstraintXml->addAttribute('columns', implode(',', $unique['columns'])); } } $fields = $metadata->fieldMappings; $id = []; foreach ($fields as $name => $field) { if (isset($field['id']) && $field['id']) { $id[$name] = $field; unset($fields[$name]); } } foreach ($metadata->associationMappings as $name => $assoc) { if (isset($assoc['id']) && $assoc['id']) { $id[$name] = [ 'fieldName' => $name, 'associationKey' => true, ]; } } if (! $metadata->isIdentifierComposite) { $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType); if ($idGeneratorType) { $id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } } if ($id) { foreach ($id as $field) { $idXml = $root->addChild('id'); $idXml->addAttribute('name', $field['fieldName']); if (isset($field['type'])) { $idXml->addAttribute('type', $field['type']); } if (isset($field['columnName'])) { $idXml->addAttribute('column', $field['columnName']); } if (isset($field['length'])) { $idXml->addAttribute('length', (string) $field['length']); } if (isset($field['associationKey']) && $field['associationKey']) { $idXml->addAttribute('association-key', 'true'); } $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType); if ($idGeneratorType) { $generatorXml = $idXml->addChild('generator'); $generatorXml->addAttribute('strategy', $idGeneratorType); $this->exportSequenceInformation($idXml, $metadata); } } } if ($fields) { foreach ($fields as $field) { $fieldXml = $root->addChild('field'); $fieldXml->addAttribute('name', $field['fieldName']); $fieldXml->addAttribute('type', $field['type']); $fieldXml->addAttribute('column', $field['columnName']); if (isset($field['length'])) { $fieldXml->addAttribute('length', (string) $field['length']); } if (isset($field['precision'])) { $fieldXml->addAttribute('precision', (string) $field['precision']); } if (isset($field['scale'])) { $fieldXml->addAttribute('scale', (string) $field['scale']); } if (isset($field['unique']) && $field['unique']) { $fieldXml->addAttribute('unique', 'true'); } if (isset($field['options'])) { $optionsXml = $fieldXml->addChild('options'); foreach ($field['options'] as $key => $value) { $optionXml = $optionsXml->addChild('option', (string) $value); $optionXml->addAttribute('name', $key); } } if (isset($field['version'])) { $fieldXml->addAttribute('version', $field['version']); } if (isset($field['columnDefinition'])) { $fieldXml->addAttribute('column-definition', $field['columnDefinition']); } if (isset($field['nullable'])) { $fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false'); } if (isset($field['notInsertable'])) { $fieldXml->addAttribute('insertable', 'false'); } if (isset($field['notUpdatable'])) { $fieldXml->addAttribute('updatable', 'false'); } } } $orderMap = [ ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_ONE, ClassMetadataInfo::MANY_TO_MANY, ]; uasort($metadata->associationMappings, static function ($m1, $m2) use (&$orderMap) { $a1 = array_search($m1['type'], $orderMap, true); $a2 = array_search($m2['type'], $orderMap, true); return strcmp((string) $a1, (string) $a2); }); foreach ($metadata->associationMappings as $associationMapping) { $associationMappingXml = null; if ($associationMapping['type'] === ClassMetadataInfo::ONE_TO_ONE) { $associationMappingXml = $root->addChild('one-to-one'); } elseif ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_ONE) { $associationMappingXml = $root->addChild('many-to-one'); } elseif ($associationMapping['type'] === ClassMetadataInfo::ONE_TO_MANY) { $associationMappingXml = $root->addChild('one-to-many'); } elseif ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_MANY) { $associationMappingXml = $root->addChild('many-to-many'); } $associationMappingXml->addAttribute('field', $associationMapping['fieldName']); $associationMappingXml->addAttribute('target-entity', $associationMapping['targetEntity']); if (isset($associationMapping['mappedBy'])) { $associationMappingXml->addAttribute('mapped-by', $associationMapping['mappedBy']); } if (isset($associationMapping['inversedBy'])) { $associationMappingXml->addAttribute('inversed-by', $associationMapping['inversedBy']); } if (isset($associationMapping['indexBy'])) { $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval'] !== false) { $associationMappingXml->addAttribute('orphan-removal', 'true'); } if (isset($associationMapping['fetch'])) { $associationMappingXml->addAttribute('fetch', $this->_getFetchModeString($associationMapping['fetch'])); } $cascade = []; if ($associationMapping['isCascadeRemove']) { $cascade[] = 'cascade-remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'cascade-persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'cascade-refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'cascade-merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'cascade-detach'; } if (count($cascade) === 5) { $cascade = ['cascade-all']; } if ($cascade) { $cascadeXml = $associationMappingXml->addChild('cascade'); foreach ($cascade as $type) { $cascadeXml->addChild($type); } } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTableXml = $associationMappingXml->addChild('join-table'); $joinTableXml->addAttribute('name', $associationMapping['joinTable']['name']); $joinColumnsXml = $joinTableXml->addChild('join-columns'); foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } } $inverseJoinColumnsXml = $joinTableXml->addChild('inverse-join-columns'); foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { $inverseJoinColumnXml = $inverseJoinColumnsXml->addChild('join-column'); $inverseJoinColumnXml->addAttribute('name', $inverseJoinColumn['name']); $inverseJoinColumnXml->addAttribute('referenced-column-name', $inverseJoinColumn['referencedColumnName']); if (isset($inverseJoinColumn['onDelete'])) { $inverseJoinColumnXml->addAttribute('on-delete', $inverseJoinColumn['onDelete']); } if (isset($inverseJoinColumn['columnDefinition'])) { $inverseJoinColumnXml->addAttribute('column-definition', $inverseJoinColumn['columnDefinition']); } if (isset($inverseJoinColumn['nullable'])) { $inverseJoinColumnXml->addAttribute('nullable', $inverseJoinColumn['nullable'] ? 'true' : 'false'); } if (isset($inverseJoinColumn['orderBy'])) { $inverseJoinColumnXml->addAttribute('order-by', $inverseJoinColumn['orderBy']); } } } if (isset($associationMapping['joinColumns'])) { $joinColumnsXml = $associationMappingXml->addChild('join-columns'); foreach ($associationMapping['joinColumns'] as $joinColumn) { $joinColumnXml = $joinColumnsXml->addChild('join-column'); $joinColumnXml->addAttribute('name', $joinColumn['name']); $joinColumnXml->addAttribute('referenced-column-name', $joinColumn['referencedColumnName']); if (isset($joinColumn['onDelete'])) { $joinColumnXml->addAttribute('on-delete', $joinColumn['onDelete']); } if (isset($joinColumn['columnDefinition'])) { $joinColumnXml->addAttribute('column-definition', $joinColumn['columnDefinition']); } if (isset($joinColumn['nullable'])) { $joinColumnXml->addAttribute('nullable', $joinColumn['nullable'] ? 'true' : 'false'); } } } if (isset($associationMapping['orderBy'])) { $orderByXml = $associationMappingXml->addChild('order-by'); foreach ($associationMapping['orderBy'] as $name => $direction) { $orderByFieldXml = $orderByXml->addChild('order-by-field'); $orderByFieldXml->addAttribute('name', $name); $orderByFieldXml->addAttribute('direction', $direction); } } } if (isset($metadata->lifecycleCallbacks) && count($metadata->lifecycleCallbacks) > 0) { $lifecycleCallbacksXml = $root->addChild('lifecycle-callbacks'); foreach ($metadata->lifecycleCallbacks as $name => $methods) { foreach ($methods as $method) { $lifecycleCallbackXml = $lifecycleCallbacksXml->addChild('lifecycle-callback'); $lifecycleCallbackXml->addAttribute('type', $name); $lifecycleCallbackXml->addAttribute('method', $method); } } } $this->processEntityListeners($metadata, $root); return $this->asXml($xml); } /** * Exports (nested) option elements. * * @param mixed[] $options */ private function exportTableOptions(SimpleXMLElement $parentXml, array $options): void { foreach ($options as $name => $option) { $isArray = is_array($option); $optionXml = $isArray ? $parentXml->addChild('option') : $parentXml->addChild('option', (string) $option); $optionXml->addAttribute('name', (string) $name); if ($isArray) { $this->exportTableOptions($optionXml, $option); } } } /** * Export sequence information (if available/configured) into the current identifier XML node */ private function exportSequenceInformation(SimpleXMLElement $identifierXmlNode, ClassMetadataInfo $metadata): void { $sequenceDefinition = $metadata->sequenceGeneratorDefinition; if (! ($metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE && $sequenceDefinition)) { return; } $sequenceGeneratorXml = $identifierXmlNode->addChild('sequence-generator'); $sequenceGeneratorXml->addAttribute('sequence-name', $sequenceDefinition['sequenceName']); $sequenceGeneratorXml->addAttribute('allocation-size', $sequenceDefinition['allocationSize']); $sequenceGeneratorXml->addAttribute('initial-value', $sequenceDefinition['initialValue']); } private function asXml(SimpleXMLElement $simpleXml): string { $dom = new DOMDocument('1.0', 'UTF-8'); $dom->loadXML($simpleXml->asXML()); $dom->formatOutput = true; return $dom->saveXML(); } private function processEntityListeners(ClassMetadataInfo $metadata, SimpleXMLElement $root): void { if (count($metadata->entityListeners) === 0) { return; } $entityListenersXml = $root->addChild('entity-listeners'); $entityListenersXmlMap = []; $this->generateEntityListenerXml($metadata, $entityListenersXmlMap, $entityListenersXml); } /** @param mixed[] $entityListenersXmlMap */ private function generateEntityListenerXml( ClassMetadataInfo $metadata, array $entityListenersXmlMap, SimpleXMLElement $entityListenersXml ): void { foreach ($metadata->entityListeners as $event => $entityListenerConfig) { foreach ($entityListenerConfig as $entityListener) { $entityListenerXml = $this->addClassToMapIfExists( $entityListenersXmlMap, $entityListener, $entityListenersXml ); $entityListenerCallbackXml = $entityListenerXml->addChild('lifecycle-callback'); $entityListenerCallbackXml->addAttribute('type', $event); $entityListenerCallbackXml->addAttribute('method', $entityListener['method']); } } } /** * @param mixed[] $entityListenersXmlMap * @param mixed[] $entityListener */ private function addClassToMapIfExists( array $entityListenersXmlMap, array $entityListener, SimpleXMLElement $entityListenersXml ): SimpleXMLElement { if (isset($entityListenersXmlMap[$entityListener['class']])) { return $entityListenersXmlMap[$entityListener['class']]; } $entityListenerXml = $entityListenersXml->addChild('entity-listener'); $entityListenerXml->addAttribute('class', $entityListener['class']); $entityListenersXmlMap[$entityListener['class']] = $entityListenerXml; return $entityListenerXml; } } orm/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php 0000644 00000022464 15120025736 0017304 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Symfony\Component\Yaml\Yaml; use function array_merge; use function count; /** * ClassMetadata exporter for Doctrine YAML mapping files. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class YamlExporter extends AbstractExporter { /** @var string */ protected $_extension = '.dcm.yml'; /** * {@inheritDoc} */ public function exportClassMetadata(ClassMetadataInfo $metadata) { $array = []; if ($metadata->isMappedSuperclass) { $array['type'] = 'mappedSuperclass'; } else { $array['type'] = 'entity'; } $metadataTable = $metadata->table ?? ['name' => null]; $array['table'] = $metadataTable['name']; if (isset($metadataTable['schema'])) { $array['schema'] = $metadataTable['schema']; } $inheritanceType = $metadata->inheritanceType; if ($inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) { $array['inheritanceType'] = $this->_getInheritanceTypeString($inheritanceType); } $column = $metadata->discriminatorColumn; if ($column) { $array['discriminatorColumn'] = $column; } $map = $metadata->discriminatorMap; if ($map) { $array['discriminatorMap'] = $map; } if ($metadata->changeTrackingPolicy !== ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT) { $array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); } if (isset($metadataTable['indexes'])) { $array['indexes'] = $metadataTable['indexes']; } if ($metadata->customRepositoryClassName) { $array['repositoryClass'] = $metadata->customRepositoryClassName; } if (isset($metadataTable['uniqueConstraints'])) { $array['uniqueConstraints'] = $metadataTable['uniqueConstraints']; } if (isset($metadataTable['options'])) { $array['options'] = $metadataTable['options']; } $fieldMappings = $metadata->fieldMappings; $ids = []; foreach ($fieldMappings as $name => $fieldMapping) { $fieldMapping['column'] = $fieldMapping['columnName']; unset($fieldMapping['columnName'], $fieldMapping['fieldName']); if ($fieldMapping['column'] === $name) { unset($fieldMapping['column']); } if (isset($fieldMapping['id']) && $fieldMapping['id']) { $ids[$name] = $fieldMapping; unset($fieldMappings[$name]); continue; } $fieldMappings[$name] = $fieldMapping; } if (! $metadata->isIdentifierComposite) { $idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType); if ($idGeneratorType) { $ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $idGeneratorType; } } $array['id'] = $ids; if ($fieldMappings) { $array['fields'] = $fieldMappings; } foreach ($metadata->associationMappings as $name => $associationMapping) { $cascade = []; if ($associationMapping['isCascadeRemove']) { $cascade[] = 'remove'; } if ($associationMapping['isCascadePersist']) { $cascade[] = 'persist'; } if ($associationMapping['isCascadeRefresh']) { $cascade[] = 'refresh'; } if ($associationMapping['isCascadeMerge']) { $cascade[] = 'merge'; } if ($associationMapping['isCascadeDetach']) { $cascade[] = 'detach'; } if (count($cascade) === 5) { $cascade = ['all']; } $associationMappingArray = [ 'targetEntity' => $associationMapping['targetEntity'], 'cascade' => $cascade, ]; if (isset($associationMapping['fetch'])) { $associationMappingArray['fetch'] = $this->_getFetchModeString($associationMapping['fetch']); } if (isset($associationMapping['id']) && $associationMapping['id'] === true) { $array['id'][$name]['associationKey'] = true; } if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $joinColumns = $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : []; $newJoinColumns = []; foreach ($joinColumns as $joinColumn) { $newJoinColumns[$joinColumn['name']]['referencedColumnName'] = $joinColumn['referencedColumnName']; if (isset($joinColumn['onDelete'])) { $newJoinColumns[$joinColumn['name']]['onDelete'] = $joinColumn['onDelete']; } } $oneToOneMappingArray = [ 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinColumns' => $newJoinColumns, 'orphanRemoval' => $associationMapping['orphanRemoval'], ]; $associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray); if ($associationMapping['type'] & ClassMetadataInfo::ONE_TO_ONE) { $array['oneToOne'][$name] = $associationMappingArray; } else { $array['manyToOne'][$name] = $associationMappingArray; } } elseif ($associationMapping['type'] === ClassMetadataInfo::ONE_TO_MANY) { $oneToManyMappingArray = [ 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'orphanRemoval' => $associationMapping['orphanRemoval'], 'orderBy' => $associationMapping['orderBy'] ?? null, ]; $associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray); $array['oneToMany'][$name] = $associationMappingArray; } elseif ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_MANY) { $manyToManyMappingArray = [ 'mappedBy' => $associationMapping['mappedBy'], 'inversedBy' => $associationMapping['inversedBy'], 'joinTable' => $associationMapping['joinTable'] ?? null, 'orderBy' => $associationMapping['orderBy'] ?? null, ]; $associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray); $array['manyToMany'][$name] = $associationMappingArray; } } if (isset($metadata->lifecycleCallbacks)) { $array['lifecycleCallbacks'] = $metadata->lifecycleCallbacks; } $array = $this->processEntityListeners($metadata, $array); return $this->yamlDump([$metadata->name => $array], 10); } /** * Dumps a PHP array to a YAML string. * * The yamlDump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param mixed[] $array PHP array * @param int $inline [optional] The level where you switch to inline YAML * * @return string A YAML string representing the original PHP array */ protected function yamlDump($array, $inline = 2) { return Yaml::dump($array, $inline); } /** * @psalm-param array<string, mixed> $array * * @psalm-return array<string, mixed>&array{entityListeners: array<class-string, array<string, array{string}>>} */ private function processEntityListeners(ClassMetadataInfo $metadata, array $array): array { if (count($metadata->entityListeners) === 0) { return $array; } $array['entityListeners'] = []; foreach ($metadata->entityListeners as $event => $entityListenerConfig) { $array = $this->processEntityListenerConfig($array, $entityListenerConfig, $event); } return $array; } /** * @psalm-param array{entityListeners: array<class-string, array<string, array{string}>>} $array * @psalm-param list<array{class: class-string, method: string}> $entityListenerConfig * * @psalm-return array{entityListeners: array<class-string, array<string, array{string}>>} */ private function processEntityListenerConfig( array $array, array $entityListenerConfig, string $event ): array { foreach ($entityListenerConfig as $entityListener) { if (! isset($array['entityListeners'][$entityListener['class']])) { $array['entityListeners'][$entityListener['class']] = []; } $array['entityListeners'][$entityListener['class']][$event] = [$entityListener['method']]; } return $array; } } orm/lib/Doctrine/ORM/Tools/Export/ClassMetadataExporter.php 0000644 00000003500 15120025736 0017643 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export; use Doctrine\Deprecations\Deprecation; /** * Class used for converting your mapping information between the * supported formats: yaml, xml, and php/annotation. * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class ClassMetadataExporter { /** @var array<string,string> */ private static $_exporterDrivers = [ 'xml' => Driver\XmlExporter::class, 'yaml' => Driver\YamlExporter::class, 'yml' => Driver\YamlExporter::class, 'php' => Driver\PhpExporter::class, 'annotation' => Driver\AnnotationExporter::class, ]; public function __construct() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8458', '%s is deprecated with no replacement', self::class ); } /** * Registers a new exporter driver class under a specified name. * * @param string $name * @param string $class * * @return void */ public static function registerExportDriver($name, $class) { self::$_exporterDrivers[$name] = $class; } /** * Gets an exporter driver instance. * * @param string $type The type to get (yml, xml, etc.). * @param string|null $dest The directory where the exporter will export to. * * @return Driver\AbstractExporter * * @throws ExportException */ public function getExporter($type, $dest = null) { if (! isset(self::$_exporterDrivers[$type])) { throw ExportException::invalidExporterDriverType($type); } $class = self::$_exporterDrivers[$type]; return new $class($dest); } } orm/lib/Doctrine/ORM/Tools/Export/ExportException.php 0000644 00000002056 15120025736 0016551 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Export; use Doctrine\ORM\Exception\ORMException; use function sprintf; /** @deprecated 2.7 This class is being removed from the ORM and won't have any replacement */ class ExportException extends ORMException { /** * @param string $type * * @return ExportException */ public static function invalidExporterDriverType($type) { return new self(sprintf( "The specified export driver '%s' does not exist", $type )); } /** * @param string $type * * @return ExportException */ public static function invalidMappingDriverType($type) { return new self(sprintf( "The mapping driver '%s' does not exist", $type )); } /** * @param string $file * * @return ExportException */ public static function attemptOverwriteExistingFile($file) { return new self("Attempting to overwrite an existing file '" . $file . "'."); } } orm/lib/Doctrine/ORM/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php 0000644 00000000547 15120025736 0024400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination\Exception; use Doctrine\ORM\Exception\ORMException; final class RowNumberOverFunctionNotEnabled extends ORMException { public static function create(): self { return new self('The RowNumberOverFunction is not intended for, nor is it enabled for use in DQL.'); } } orm/lib/Doctrine/ORM/Tools/Pagination/CountOutputWalker.php 0000644 00000011423 15120025736 0017676 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\SqlWalker; use RuntimeException; use function array_diff; use function array_keys; use function count; use function implode; use function reset; use function sprintf; /** * Wraps the query in order to accurately count the root objects. * * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like: * SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>)) * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL) * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery * that will most likely be executed next can be read from the native SQL cache. * * @psalm-import-type QueryComponent from Parser */ class CountOutputWalker extends SqlWalker { /** @var AbstractPlatform */ private $platform; /** @var ResultSetMapping */ private $rsm; /** * Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param Query $query * @param ParserResult $parserResult * @param mixed[] $queryComponents * @psalm-param array<string, QueryComponent> $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); parent::__construct($query, $parserResult, $queryComponents); } /** * {@inheritDoc} */ public function walkSelectStatement(SelectStatement $AST) { if ($this->platform instanceof SQLServerPlatform) { $AST->orderByClause = null; } $sql = parent::walkSelectStatement($AST); if ($AST->groupByClause) { return sprintf( 'SELECT COUNT(*) AS dctrn_count FROM (%s) dctrn_table', $sql ); } // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = []; foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) { if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) { if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) !== count($sqlIdentifier)) { throw new RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } // Build the counter query return sprintf( 'SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table', implode(', ', $sqlIdentifier), $sql ); } } orm/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php 0000644 00000004447 15120025736 0016465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST\AggregateExpression; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\AST\SelectExpression; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\TreeWalkerAdapter; use RuntimeException; use function count; use function reset; /** * Replaces the selectClause of the AST with a COUNT statement. */ class CountWalker extends TreeWalkerAdapter { /** * Distinct mode hint name. */ public const HINT_DISTINCT = 'doctrine_paginator.distinct'; public function walkSelectStatement(SelectStatement $AST) { if ($AST->havingClause) { throw new RuntimeException('Cannot count query that uses a HAVING clause. Use the output walkers for pagination'); } // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($rootClass->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName ); $pathExpression->type = $pathType; $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT); $AST->selectClause->selectExpressions = [ new SelectExpression( new AggregateExpression('count', $pathExpression, $distinct), null ), ]; // ORDER BY is not needed, only increases query execution through unnecessary sorting. $AST->orderByClause = null; } } orm/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php 0000644 00000050641 15120025736 0021431 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLAnywherePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\OrderByClause; use Doctrine\ORM\Query\AST\PartialObjectExpression; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\AST\SelectExpression; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\SqlWalker; use RuntimeException; use function array_diff; use function array_keys; use function assert; use function count; use function implode; use function in_array; use function is_string; use function method_exists; use function preg_replace; use function reset; use function sprintf; use function strrpos; use function substr; /** * Wraps the query in order to select root entity IDs for pagination. * * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like: * SELECT DISTINCT <id> FROM (<original SQL>) LIMIT x OFFSET y * * Works with composite keys but cannot deal with queries that have multiple * root entities (e.g. `SELECT f, b from Foo, Bar`) * * @psalm-import-type QueryComponent from Parser */ class LimitSubqueryOutputWalker extends SqlWalker { private const ORDER_BY_PATH_EXPRESSION = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i'; /** @var AbstractPlatform */ private $platform; /** @var ResultSetMapping */ private $rsm; /** @var int */ private $firstResult; /** @var int */ private $maxResults; /** @var EntityManagerInterface */ private $em; /** * The quote strategy. * * @var QuoteStrategy */ private $quoteStrategy; /** @var list<PathExpression> */ private $orderByPathExpressions = []; /** * @var bool We don't want to add path expressions from sub-selects into the select clause of the containing query. * This state flag simply keeps track on whether we are walking on a subquery or not */ private $inSubSelect = false; /** * Stores various parameters that are otherwise unavailable * because Doctrine\ORM\Query\SqlWalker keeps everything private without * accessors. * * @param Query $query * @param ParserResult $parserResult * @param mixed[] $queryComponents * @psalm-param array<string, QueryComponent> $queryComponents */ public function __construct($query, $parserResult, array $queryComponents) { $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); $this->rsm = $parserResult->getResultSetMapping(); // Reset limit and offset $this->firstResult = $query->getFirstResult(); $this->maxResults = $query->getMaxResults(); $query->setFirstResult(0)->setMaxResults(null); $this->em = $query->getEntityManager(); $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy(); parent::__construct($query, $parserResult, $queryComponents); } /** * Check if the platform supports the ROW_NUMBER window function. */ private function platformSupportsRowNumber(): bool { return $this->platform instanceof PostgreSQLPlatform || $this->platform instanceof SQLServerPlatform || $this->platform instanceof OraclePlatform || $this->platform instanceof SQLAnywherePlatform || $this->platform instanceof DB2Platform || (method_exists($this->platform, 'supportsRowNumberFunction') && $this->platform->supportsRowNumberFunction()); } /** * Rebuilds a select statement's order by clause for use in a * ROW_NUMBER() OVER() expression. */ private function rebuildOrderByForRowNumber(SelectStatement $AST): void { $orderByClause = $AST->orderByClause; $selectAliasToExpressionMap = []; // Get any aliases that are available for select expressions. foreach ($AST->selectClause->selectExpressions as $selectExpression) { $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression; } // Rebuild string orderby expressions to use the select expression they're referencing foreach ($orderByClause->orderByItems as $orderByItem) { if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) { $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression]; } } $func = new RowNumberOverFunction('dctrn_rownum'); $func->orderByClause = $AST->orderByClause; $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true); // No need for an order by clause, we'll order by rownum in the outer query. $AST->orderByClause = null; } /** * {@inheritDoc} */ public function walkSelectStatement(SelectStatement $AST) { if ($this->platformSupportsRowNumber()) { return $this->walkSelectStatementWithRowNumber($AST); } return $this->walkSelectStatementWithoutRowNumber($AST); } /** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. * This method is for use with platforms which support ROW_NUMBER. * * @return string * * @throws RuntimeException */ public function walkSelectStatementWithRowNumber(SelectStatement $AST) { $hasOrderBy = false; $outerOrderBy = ' ORDER BY dctrn_minrownum ASC'; $orderGroupBy = ''; if ($AST->orderByClause instanceof OrderByClause) { $hasOrderBy = true; $this->rebuildOrderByForRowNumber($AST); } $innerSql = $this->getInnerSQL($AST); $sqlIdentifier = $this->getSQLIdentifier($AST); if ($hasOrderBy) { $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier); $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum'; } // Build the counter query $sql = sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql ); if ($hasOrderBy) { $sql .= $orderGroupBy . $outerOrderBy; } // Apply the limit and offset. $sql = $this->platform->modifyLimitQuery( $sql, $this->maxResults, $this->firstResult ); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } return $sql; } /** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. * This method is for platforms which DO NOT support ROW_NUMBER. * * @param bool $addMissingItemsFromOrderByToSelect * * @return string * * @throws RuntimeException */ public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, $addMissingItemsFromOrderByToSelect = true) { // We don't want to call this recursively! if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) { // In the case of ordering a query by columns from joined tables, we // must add those columns to the select clause of the query BEFORE // the SQL is generated. $this->addMissingItemsFromOrderByToSelect($AST); } // Remove order by clause from the inner query // It will be re-appended in the outer select generated by this method $orderByClause = $AST->orderByClause; $AST->orderByClause = null; $innerSql = $this->getInnerSQL($AST); $sqlIdentifier = $this->getSQLIdentifier($AST); // Build the counter query $sql = sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql ); // http://www.doctrine-project.org/jira/browse/DDC-1958 $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause); // Apply the limit and offset. $sql = $this->platform->modifyLimitQuery( $sql, $this->maxResults, $this->firstResult ); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } // Restore orderByClause $AST->orderByClause = $orderByClause; return $sql; } /** * Finds all PathExpressions in an AST's OrderByClause, and ensures that * the referenced fields are present in the SelectClause of the passed AST. */ private function addMissingItemsFromOrderByToSelect(SelectStatement $AST): void { $this->orderByPathExpressions = []; // We need to do this in another walker because otherwise we'll end up // polluting the state of this one. $walker = clone $this; // This will populate $orderByPathExpressions via // LimitSubqueryOutputWalker::walkPathExpression, which will be called // as the select statement is walked. We'll end up with an array of all // path expressions referenced in the query. $walker->walkSelectStatementWithoutRowNumber($AST, false); $orderByPathExpressions = $walker->getOrderByPathExpressions(); // Get a map of referenced identifiers to field names. $selects = []; foreach ($orderByPathExpressions as $pathExpression) { assert($pathExpression->field !== null); $idVar = $pathExpression->identificationVariable; $field = $pathExpression->field; if (! isset($selects[$idVar])) { $selects[$idVar] = []; } $selects[$idVar][$field] = true; } // Loop the select clause of the AST and exclude items from $select // that are already being selected in the query. foreach ($AST->selectClause->selectExpressions as $selectExpression) { if ($selectExpression instanceof SelectExpression) { $idVar = $selectExpression->expression; if (! is_string($idVar)) { continue; } $field = $selectExpression->fieldIdentificationVariable; if ($field === null) { // No need to add this select, as we're already fetching the whole object. unset($selects[$idVar]); } else { unset($selects[$idVar][$field]); } } } // Add select items which were not excluded to the AST's select clause. foreach ($selects as $idVar => $fields) { $AST->selectClause->selectExpressions[] = new SelectExpression(new PartialObjectExpression($idVar, array_keys($fields)), null, true); } } /** * Generates new SQL for statements with an order by clause * * @param mixed[] $sqlIdentifier */ private function preserveSqlOrdering( array $sqlIdentifier, string $innerSql, string $sql, ?OrderByClause $orderByClause ): string { // If the sql statement has an order by clause, we need to wrap it in a new select distinct statement if (! $orderByClause) { return $sql; } // now only select distinct identifier return sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $this->recreateInnerSql($orderByClause, $sqlIdentifier, $innerSql) ); } /** * Generates a new SQL statement for the inner query to keep the correct sorting * * @param mixed[] $identifiers */ private function recreateInnerSql( OrderByClause $orderByClause, array $identifiers, string $innerSql ): string { [$searchPatterns, $replacements] = $this->generateSqlAliasReplacements(); $orderByItems = []; foreach ($orderByClause->orderByItems as $orderByItem) { // Walk order by item to get string representation of it and // replace path expressions in the order by clause with their column alias $orderByItemString = preg_replace( $searchPatterns, $replacements, $this->walkOrderByItem($orderByItem) ); $orderByItems[] = $orderByItemString; $identifier = substr($orderByItemString, 0, strrpos($orderByItemString, ' ')); if (! in_array($identifier, $identifiers, true)) { $identifiers[] = $identifier; } } return $sql = sprintf( 'SELECT DISTINCT %s FROM (%s) dctrn_result_inner ORDER BY %s', implode(', ', $identifiers), $innerSql, implode(', ', $orderByItems) ); } /** * @return string[][] * @psalm-return array{0: list<string>, 1: list<string>} */ private function generateSqlAliasReplacements(): array { $aliasMap = $searchPatterns = $replacements = $metadataList = []; // Generate DQL alias -> SQL table alias mapping foreach (array_keys($this->rsm->aliasMap) as $dqlAlias) { $metadataList[$dqlAlias] = $class = $this->getMetadataForDqlAlias($dqlAlias); $aliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias); } // Generate search patterns for each field's path expression in the order by clause foreach ($this->rsm->fieldMappings as $fieldAlias => $fieldName) { $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias]; $class = $metadataList[$dqlAliasForFieldAlias]; // If the field is from a joined child table, we won't be ordering on it. if (! isset($class->fieldMappings[$fieldName])) { continue; } $fieldMapping = $class->fieldMappings[$fieldName]; // Get the proper column name as will appear in the select list $columnName = $this->quoteStrategy->getColumnName( $fieldName, $metadataList[$dqlAliasForFieldAlias], $this->em->getConnection()->getDatabasePlatform() ); // Get the SQL table alias for the entity and field $sqlTableAliasForFieldAlias = $aliasMap[$dqlAliasForFieldAlias]; if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) { // Field was declared in a parent class, so we need to get the proper SQL table alias // for the joined parent table. $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']); if (! $otherClassMetadata->isMappedSuperclass) { $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias); } } // Compose search and replace patterns $searchPatterns[] = sprintf(self::ORDER_BY_PATH_EXPRESSION, $sqlTableAliasForFieldAlias, $columnName); $replacements[] = $fieldAlias; } return [$searchPatterns, $replacements]; } /** * getter for $orderByPathExpressions * * @return list<PathExpression> */ public function getOrderByPathExpressions() { return $this->orderByPathExpressions; } /** * @throws OptimisticLockException * @throws QueryException */ private function getInnerSQL(SelectStatement $AST): string { // Set every select expression as visible(hidden = false) to // make $AST have scalar mappings properly - this is relevant for referencing selected // fields from outside the subquery, for example in the ORDER BY segment $hiddens = []; foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $hiddens[$idx] = $expr->hiddenAliasResultVariable; $expr->hiddenAliasResultVariable = false; } $innerSql = parent::walkSelectStatement($AST); // Restore hiddens foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $expr->hiddenAliasResultVariable = $hiddens[$idx]; } return $innerSql; } /** @return string[] */ private function getSQLIdentifier(SelectStatement $AST): array { // Find out the SQL alias of the identifier column of the root entity. // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT. // So for now, it's not supported. // Get the root entity and alias from the AST fromClause. $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) !== 1) { throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = []; foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) { if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) { if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($sqlIdentifier) === 0) { throw new RuntimeException('The Paginator does not support Queries which only yield ScalarResults.'); } if (count($rootIdentifier) !== count($sqlIdentifier)) { throw new RuntimeException(sprintf( 'Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))) )); } return $sqlIdentifier; } /** * {@inheritDoc} */ public function walkPathExpression($pathExpr) { if (! $this->inSubSelect && ! $this->platformSupportsRowNumber() && ! in_array($pathExpr, $this->orderByPathExpressions, true)) { $this->orderByPathExpressions[] = $pathExpr; } return parent::walkPathExpression($pathExpr); } /** * {@inheritDoc} */ public function walkSubSelect($subselect) { $this->inSubSelect = true; $sql = parent::walkSubselect($subselect); $this->inSubSelect = false; return $sql; } } orm/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php 0000644 00000013227 15120025736 0020207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\Functions\IdentityFunction; use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\AST\SelectExpression; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\TreeWalkerAdapter; use RuntimeException; use function count; use function is_string; use function reset; /** * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent. */ class LimitSubqueryWalker extends TreeWalkerAdapter { public const IDENTIFIER_TYPE = 'doctrine_paginator.id.type'; public const FORCE_DBAL_TYPE_CONVERSION = 'doctrine_paginator.scalar_result.force_dbal_type_conversion'; /** * Counter for generating unique order column aliases. * * @var int */ private $aliasCounter = 0; public function walkSelectStatement(SelectStatement $AST) { // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $this->validate($AST); $identifier = $rootClass->getSingleIdentifierFieldName(); if (isset($rootClass->associationMappings[$identifier])) { throw new RuntimeException('Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.'); } $this->_getQuery()->setHint( self::IDENTIFIER_TYPE, Type::getType($rootClass->fieldMappings[$identifier]['type']) ); $this->_getQuery()->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true); $pathExpression = new PathExpression( PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifier ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; $AST->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')]; $AST->selectClause->isDistinct = true; if (! isset($AST->orderByClause)) { return; } $queryComponents = $this->_getQueryComponents(); foreach ($AST->orderByClause->orderByItems as $item) { if ($item->expression instanceof PathExpression) { $AST->selectClause->selectExpressions[] = new SelectExpression( $this->createSelectExpressionItem($item->expression), '_dctrn_ord' . $this->aliasCounter++ ); continue; } if (is_string($item->expression) && isset($queryComponents[$item->expression])) { $qComp = $queryComponents[$item->expression]; if (isset($qComp['resultVariable'])) { $AST->selectClause->selectExpressions[] = new SelectExpression( $qComp['resultVariable'], $item->expression ); } } } } /** * Validate the AST to ensure that this walker is able to properly manipulate it. */ private function validate(SelectStatement $AST): void { // Prevent LimitSubqueryWalker from being used with queries that include // a limit, a fetched to-many join, and an order by condition that // references a column from the fetch joined table. $queryComponents = $this->getQueryComponents(); $query = $this->_getQuery(); $from = $AST->fromClause->identificationVariableDeclarations; $fromRoot = reset($from); if ( $query instanceof Query && $query->getMaxResults() !== null && $AST->orderByClause && count($fromRoot->joins) ) { // Check each orderby item. // TODO: check complex orderby items too... foreach ($AST->orderByClause->orderByItems as $orderByItem) { $expression = $orderByItem->expression; if ( $orderByItem->expression instanceof PathExpression && isset($queryComponents[$expression->identificationVariable]) ) { $queryComponent = $queryComponents[$expression->identificationVariable]; if ( isset($queryComponent['parent']) && isset($queryComponent['relation']) && $queryComponent['relation']['type'] & ClassMetadata::TO_MANY ) { throw new RuntimeException('Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers.'); } } } } } /** * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name). * * @return IdentityFunction|PathExpression */ private function createSelectExpressionItem(PathExpression $pathExpression): Node { if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { $identity = new IdentityFunction('identity'); $identity->pathExpression = clone $pathExpression; return $identity; } return clone $pathExpression; } } orm/lib/Doctrine/ORM/Tools/Pagination/Paginator.php 0000644 00000020246 15120025736 0016146 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use ArrayIterator; use Countable; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\NoResultException; use Doctrine\ORM\Query; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\QueryBuilder; use IteratorAggregate; use ReturnTypeWillChange; use Traversable; use function array_key_exists; use function array_map; use function array_sum; use function assert; use function is_string; /** * The paginator can handle various complex scenarios with DQL. * * @template-covariant T * @implements IteratorAggregate<array-key, T> */ class Paginator implements Countable, IteratorAggregate { use SQLResultCasing; /** @var Query */ private $query; /** @var bool */ private $fetchJoinCollection; /** @var bool|null */ private $useOutputWalkers; /** @var int|null */ private $count; /** * @param Query|QueryBuilder $query A Doctrine ORM query or query builder. * @param bool $fetchJoinCollection Whether the query joins a collection (true by default). */ public function __construct($query, $fetchJoinCollection = true) { if ($query instanceof QueryBuilder) { $query = $query->getQuery(); } $this->query = $query; $this->fetchJoinCollection = (bool) $fetchJoinCollection; } /** * Returns the query. * * @return Query */ public function getQuery() { return $this->query; } /** * Returns whether the query joins a collection. * * @return bool Whether the query joins a collection. */ public function getFetchJoinCollection() { return $this->fetchJoinCollection; } /** * Returns whether the paginator will use an output walker. * * @return bool|null */ public function getUseOutputWalkers() { return $this->useOutputWalkers; } /** * Sets whether the paginator will use an output walker. * * @param bool|null $useOutputWalkers * * @return $this * @psalm-return static<T> */ public function setUseOutputWalkers($useOutputWalkers) { $this->useOutputWalkers = $useOutputWalkers; return $this; } /** * {@inheritDoc} * * @return int */ #[ReturnTypeWillChange] public function count() { if ($this->count === null) { try { $this->count = (int) array_sum(array_map('current', $this->getCountQuery()->getScalarResult())); } catch (NoResultException $e) { $this->count = 0; } } return $this->count; } /** * {@inheritDoc} * * @return Traversable * @psalm-return Traversable<array-key, T> */ #[ReturnTypeWillChange] public function getIterator() { $offset = $this->query->getFirstResult(); $length = $this->query->getMaxResults(); if ($this->fetchJoinCollection && $length !== null) { $subQuery = $this->cloneQuery($this->query); if ($this->useOutputWalker($subQuery)) { $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class); } else { $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class); $this->unbindUnusedQueryParams($subQuery); } $subQuery->setFirstResult($offset)->setMaxResults($length); $foundIdRows = $subQuery->getScalarResult(); // don't do this for an empty id array if ($foundIdRows === []) { return new ArrayIterator([]); } $whereInQuery = $this->cloneQuery($this->query); $ids = array_map('current', $foundIdRows); $this->appendTreeWalker($whereInQuery, WhereInWalker::class); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_HAS_IDS, true); $whereInQuery->setFirstResult(0)->setMaxResults(null); $whereInQuery->setCacheable($this->query->isCacheable()); $databaseIds = $this->convertWhereInIdentifiersToDatabaseValues($ids); $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds); $result = $whereInQuery->getResult($this->query->getHydrationMode()); } else { $result = $this->cloneQuery($this->query) ->setMaxResults($length) ->setFirstResult($offset) ->setCacheable($this->query->isCacheable()) ->getResult($this->query->getHydrationMode()); } return new ArrayIterator($result); } private function cloneQuery(Query $query): Query { $cloneQuery = clone $query; $cloneQuery->setParameters(clone $query->getParameters()); $cloneQuery->setCacheable(false); foreach ($query->getHints() as $name => $value) { $cloneQuery->setHint($name, $value); } return $cloneQuery; } /** * Determines whether to use an output walker for the query. */ private function useOutputWalker(Query $query): bool { if ($this->useOutputWalkers === null) { return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false; } return $this->useOutputWalkers; } /** * Appends a custom tree walker to the tree walkers hint. * * @psalm-param class-string $walkerClass */ private function appendTreeWalker(Query $query, string $walkerClass): void { $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); if ($hints === false) { $hints = []; } $hints[] = $walkerClass; $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints); } /** * Returns Query prepared to count. */ private function getCountQuery(): Query { $countQuery = $this->cloneQuery($this->query); if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); } if ($this->useOutputWalker($countQuery)) { $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win $rsm = new ResultSetMapping(); $rsm->addScalarResult($this->getSQLResultCasing($platform, 'dctrn_count'), 'count'); $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class); $countQuery->setResultSetMapping($rsm); } else { $this->appendTreeWalker($countQuery, CountWalker::class); $this->unbindUnusedQueryParams($countQuery); } $countQuery->setFirstResult(0)->setMaxResults(null); return $countQuery; } private function unbindUnusedQueryParams(Query $query): void { $parser = new Parser($query); $parameterMappings = $parser->parse()->getParameterMappings(); /** @var Collection|Parameter[] $parameters */ $parameters = $query->getParameters(); foreach ($parameters as $key => $parameter) { $parameterName = $parameter->getName(); if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) { unset($parameters[$key]); } } $query->setParameters($parameters); } /** * @param mixed[] $identifiers * * @return mixed[] */ private function convertWhereInIdentifiersToDatabaseValues(array $identifiers): array { $query = $this->cloneQuery($this->query); $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class); $connection = $this->query->getEntityManager()->getConnection(); $type = $query->getSQL(); assert(is_string($type)); return array_map(static function ($id) use ($connection, $type) { return $connection->convertToDatabaseValue($id, $type); }, $identifiers); } } orm/lib/Doctrine/ORM/Tools/Pagination/RootTypeWalker.php 0000644 00000003200 15120025736 0017144 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Utility\PersisterHelper; use RuntimeException; use function count; use function reset; /** * Infers the DBAL type of the #Id (identifier) column of the given query's root entity, and * returns it in place of a real SQL statement. * * Obtaining this type is a necessary intermediate step for \Doctrine\ORM\Tools\Pagination\Paginator. * We can best do this from a tree walker because it gives us access to the AST. * * Returning the type instead of a "real" SQL statement is a slight hack. However, it has the * benefit that the DQL -> root entity id type resolution can be cached in the query cache. */ final class RootTypeWalker extends SqlWalker { public function walkSelectStatement(AST\SelectStatement $AST): string { // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new RuntimeException('Can only process queries that select only one FROM component'); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); return PersisterHelper::getTypeOfField( $identifierFieldName, $rootClass, $this->getQuery() ->getEntityManager() )[0]; } } orm/lib/Doctrine/ORM/Tools/Pagination/RowNumberOverFunction.php 0000644 00000001722 15120025736 0020502 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\AST\OrderByClause; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Tools\Pagination\Exception\RowNumberOverFunctionNotEnabled; use function trim; /** * RowNumberOverFunction * * Provides ROW_NUMBER() OVER(ORDER BY...) construct for use in LimitSubqueryOutputWalker */ class RowNumberOverFunction extends FunctionNode { /** @var OrderByClause */ public $orderByClause; /** @inheritDoc */ public function getSql(SqlWalker $sqlWalker) { return 'ROW_NUMBER() OVER(' . trim($sqlWalker->walkOrderByClause( $this->orderByClause )) . ')'; } /** * @throws RowNumberOverFunctionNotEnabled * * @inheritDoc */ public function parse(Parser $parser) { throw RowNumberOverFunctionNotEnabled::create(); } } orm/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php 0000644 00000011260 15120025736 0016725 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools\Pagination; use Doctrine\ORM\Query\AST\ArithmeticExpression; use Doctrine\ORM\Query\AST\ConditionalExpression; use Doctrine\ORM\Query\AST\ConditionalFactor; use Doctrine\ORM\Query\AST\ConditionalPrimary; use Doctrine\ORM\Query\AST\ConditionalTerm; use Doctrine\ORM\Query\AST\InListExpression; use Doctrine\ORM\Query\AST\InputParameter; use Doctrine\ORM\Query\AST\NullComparisonExpression; use Doctrine\ORM\Query\AST\PathExpression; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\AST\SimpleArithmeticExpression; use Doctrine\ORM\Query\AST\WhereClause; use Doctrine\ORM\Query\TreeWalkerAdapter; use RuntimeException; use function count; use function reset; /** * Appends a condition equivalent to "WHERE IN (:dpid_1, :dpid_2, ...)" to the whereClause of the AST. * * The parameter namespace (dpid) is defined by * the PAGINATOR_ID_ALIAS * * The HINT_PAGINATOR_HAS_IDS query hint indicates whether there are * any ids in the parameter at all. */ class WhereInWalker extends TreeWalkerAdapter { /** * ID Count hint name. */ public const HINT_PAGINATOR_HAS_IDS = 'doctrine.paginator_has_ids'; /** * Primary key alias for query. */ public const PAGINATOR_ID_ALIAS = 'dpid'; public function walkSelectStatement(SelectStatement $AST) { // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->getMetadataForDqlAlias($rootAlias); $identifierFieldName = $rootClass->getSingleIdentifierFieldName(); $pathType = PathExpression::TYPE_STATE_FIELD; if (isset($rootClass->associationMappings[$identifierFieldName])) { $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; } $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName); $pathExpression->type = $pathType; $hasIds = $this->_getQuery()->getHint(self::HINT_PAGINATOR_HAS_IDS); if ($hasIds) { $arithmeticExpression = new ArithmeticExpression(); $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression( [$pathExpression] ); $expression = new InListExpression( $arithmeticExpression, [new InputParameter(':' . self::PAGINATOR_ID_ALIAS)] ); } else { $expression = new NullComparisonExpression($pathExpression); } $conditionalPrimary = new ConditionalPrimary(); $conditionalPrimary->simpleConditionalExpression = $expression; if ($AST->whereClause) { if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) { $AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary; } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) { $AST->whereClause->conditionalExpression = new ConditionalExpression( [ new ConditionalTerm( [ $AST->whereClause->conditionalExpression, $conditionalPrimary, ] ), ] ); } elseif ( $AST->whereClause->conditionalExpression instanceof ConditionalExpression || $AST->whereClause->conditionalExpression instanceof ConditionalFactor ) { $tmpPrimary = new ConditionalPrimary(); $tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; $AST->whereClause->conditionalExpression = new ConditionalTerm( [ $tmpPrimary, $conditionalPrimary, ] ); } } else { $AST->whereClause = new WhereClause( new ConditionalExpression( [new ConditionalTerm([$conditionalPrimary])] ) ); } } } orm/lib/Doctrine/ORM/Tools/AttachEntityListenersListener.php 0000644 00000003475 15120025736 0020136 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder; use function ltrim; /** * Mechanism to programmatically attach entity listeners. */ class AttachEntityListenersListener { /** @var mixed[][] */ private $entityListeners = []; /** * Adds an entity listener for a specific entity. * * @param string $entityClass The entity to attach the listener. * @param string $listenerClass The listener class. * @param string|null $eventName The entity lifecycle event. * @param string|null $listenerCallback The listener callback method or NULL to use $eventName. * * @return void */ public function addEntityListener($entityClass, $listenerClass, $eventName, $listenerCallback = null) { $this->entityListeners[ltrim($entityClass, '\\')][] = [ 'event' => $eventName, 'class' => $listenerClass, 'method' => $listenerCallback ?: $eventName, ]; } /** * Processes event and attach the entity listener. * * @return void */ public function loadClassMetadata(LoadClassMetadataEventArgs $event) { $metadata = $event->getClassMetadata(); if (! isset($this->entityListeners[$metadata->name])) { return; } foreach ($this->entityListeners[$metadata->name] as $listener) { if ($listener['event'] === null) { EntityListenerBuilder::bindEntityListener($metadata, $listener['class']); } else { $metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']); } } unset($this->entityListeners[$metadata->name]); } } orm/lib/Doctrine/ORM/Tools/ConvertDoctrine1Schema.php 0000644 00000024256 15120025736 0016450 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\Inflector\InflectorFactory; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Symfony\Component\Yaml\Yaml; use function array_merge; use function count; use function explode; use function file_get_contents; use function glob; use function in_array; use function is_array; use function is_dir; use function is_string; use function preg_match; use function strtolower; /** * Class to help with converting Doctrine 1 schema files to Doctrine 2 mapping files * * @deprecated This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class ConvertDoctrine1Schema { /** @var mixed[] */ private $from; /** @var array<string,string> */ private $legacyTypeMap = [ // TODO: This list may need to be updated 'clob' => 'text', 'timestamp' => 'datetime', 'enum' => 'string', ]; /** * Constructor passes the directory or array of directories * to convert the Doctrine 1 schema files from. * * @param string[]|string $from * @psalm-param list<string>|string $from */ public function __construct($from) { $this->from = (array) $from; Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8458', '%s is deprecated with no replacement', self::class ); } /** * Gets an array of ClassMetadataInfo instances from the passed * Doctrine 1 schema. * * @return ClassMetadataInfo[] An array of ClassMetadataInfo instances * @psalm-return list<ClassMetadataInfo> */ public function getMetadata() { $schema = []; foreach ($this->from as $path) { if (is_dir($path)) { $files = glob($path . '/*.yml'); foreach ($files as $file) { $schema = array_merge($schema, (array) Yaml::parse(file_get_contents($file))); } } else { $schema = array_merge($schema, (array) Yaml::parse(file_get_contents($path))); } } $metadatas = []; foreach ($schema as $className => $mappingInformation) { $metadatas[] = $this->convertToClassMetadataInfo($className, $mappingInformation); } return $metadatas; } /** * @param mixed[] $mappingInformation * @psalm-param class-string $className */ private function convertToClassMetadataInfo( string $className, array $mappingInformation ): ClassMetadataInfo { $metadata = new ClassMetadataInfo($className); $this->convertTableName($className, $mappingInformation, $metadata); $this->convertColumns($className, $mappingInformation, $metadata); $this->convertIndexes($className, $mappingInformation, $metadata); $this->convertRelations($className, $mappingInformation, $metadata); return $metadata; } /** @param mixed[] $model */ private function convertTableName(string $className, array $model, ClassMetadataInfo $metadata): void { if (isset($model['tableName']) && $model['tableName']) { $e = explode('.', $model['tableName']); if (count($e) > 1) { $metadata->table['schema'] = $e[0]; $metadata->table['name'] = $e[1]; } else { $metadata->table['name'] = $e[0]; } } } /** @param mixed[] $model */ private function convertColumns( string $className, array $model, ClassMetadataInfo $metadata ): void { $id = false; if (isset($model['columns']) && $model['columns']) { foreach ($model['columns'] as $name => $column) { $fieldMapping = $this->convertColumn($className, $name, $column, $metadata); if (isset($fieldMapping['id']) && $fieldMapping['id']) { $id = true; } } } if (! $id) { $fieldMapping = [ 'fieldName' => 'id', 'columnName' => 'id', 'type' => 'integer', 'id' => true, ]; $metadata->mapField($fieldMapping); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } } /** * @param string|mixed[] $column * * @return mixed[] * * @throws ToolsException */ private function convertColumn( string $className, string $name, $column, ClassMetadataInfo $metadata ): array { if (is_string($column)) { $string = $column; $column = []; $column['type'] = $string; } if (! isset($column['name'])) { $column['name'] = $name; } // check if a column alias was used (column_name as field_name) if (preg_match('/(\w+)\sas\s(\w+)/i', $column['name'], $matches)) { $name = $matches[1]; $column['name'] = $name; $column['alias'] = $matches[2]; } if (preg_match('/([a-zA-Z]+)\(([0-9]+)\)/', $column['type'], $matches)) { $column['type'] = $matches[1]; $column['length'] = $matches[2]; } $column['type'] = strtolower($column['type']); // check if legacy column type (1.x) needs to be mapped to a 2.0 one if (isset($this->legacyTypeMap[$column['type']])) { $column['type'] = $this->legacyTypeMap[$column['type']]; } if (! Type::hasType($column['type'])) { throw ToolsException::couldNotMapDoctrine1Type($column['type']); } $fieldMapping = [ 'nullable' => ! ($column['notnull'] ?? true), // Doctrine 1 columns are nullable by default ]; if (isset($column['primary'])) { $fieldMapping['id'] = true; } $fieldMapping['fieldName'] = $column['alias'] ?? $name; $fieldMapping['columnName'] = $column['name']; $fieldMapping['type'] = $column['type']; if (isset($column['length'])) { $fieldMapping['length'] = $column['length']; } $allowed = ['precision', 'scale', 'unique', 'options', 'version']; foreach ($column as $key => $value) { if (in_array($key, $allowed, true)) { $fieldMapping[$key] = $value; } } $metadata->mapField($fieldMapping); if (isset($column['autoincrement'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } elseif (isset($column['sequence'])) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE); $definition = [ 'sequenceName' => (string) (is_array($column['sequence']) ? $column['sequence']['name'] : $column['sequence']), ]; if (isset($column['sequence']['size'])) { $definition['allocationSize'] = (int) $column['sequence']['size']; } if (isset($column['sequence']['value'])) { $definition['initialValue'] = (int) $column['sequence']['value']; } $metadata->setSequenceGeneratorDefinition($definition); } return $fieldMapping; } /** @param mixed[] $model */ private function convertIndexes( string $className, array $model, ClassMetadataInfo $metadata ): void { if (empty($model['indexes'])) { return; } foreach ($model['indexes'] as $name => $index) { $type = isset($index['type']) && $index['type'] === 'unique' ? 'uniqueConstraints' : 'indexes'; $metadata->table[$type][$name] = [ 'columns' => $index['fields'], ]; } } /** @param mixed[] $model */ private function convertRelations( string $className, array $model, ClassMetadataInfo $metadata ): void { if (empty($model['relations'])) { return; } $inflector = InflectorFactory::create()->build(); foreach ($model['relations'] as $name => $relation) { if (! isset($relation['alias'])) { $relation['alias'] = $name; } if (! isset($relation['class'])) { $relation['class'] = $name; } if (! isset($relation['local'])) { $relation['local'] = $inflector->tableize($relation['class']); } if (! isset($relation['foreign'])) { $relation['foreign'] = 'id'; } if (! isset($relation['foreignAlias'])) { $relation['foreignAlias'] = $className; } if (isset($relation['refClass'])) { $type = 'many'; $foreignType = 'many'; $joinColumns = []; } else { $type = $relation['type'] ?? 'one'; $foreignType = $relation['foreignType'] ?? 'many'; $joinColumns = [ [ 'name' => $relation['local'], 'referencedColumnName' => $relation['foreign'], 'onDelete' => $relation['onDelete'] ?? null, ], ]; } if ($type === 'one' && $foreignType === 'one') { $method = 'mapOneToOne'; } elseif ($type === 'many' && $foreignType === 'many') { $method = 'mapManyToMany'; } else { $method = 'mapOneToMany'; } $associationMapping = []; $associationMapping['fieldName'] = $relation['alias']; $associationMapping['targetEntity'] = $relation['class']; $associationMapping['mappedBy'] = $relation['foreignAlias']; $associationMapping['joinColumns'] = $joinColumns; $metadata->$method($associationMapping); } } } orm/lib/Doctrine/ORM/Tools/DebugUnitOfWorkListener.php 0000644 00000012016 15120025736 0016651 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\UnitOfWork; use Doctrine\Persistence\Proxy; use ReflectionObject; use function count; use function fclose; use function fopen; use function fwrite; use function gettype; use function is_object; use function spl_object_id; /** * Use this logger to dump the identity map during the onFlush event. This is useful for debugging * weird UnitOfWork behavior with complex operations. */ class DebugUnitOfWorkListener { /** @var string */ private $file; /** @var string */ private $context; /** * Pass a stream and context information for the debugging session. * * The stream can be php://output to print to the screen. * * @param string $file * @param string $context */ public function __construct($file = 'php://output', $context = '') { $this->file = $file; $this->context = $context; } /** @return void */ public function onFlush(OnFlushEventArgs $args) { $this->dumpIdentityMap($args->getObjectManager()); } /** * Dumps the contents of the identity map into a stream. * * @return void */ public function dumpIdentityMap(EntityManagerInterface $em) { $uow = $em->getUnitOfWork(); $identityMap = $uow->getIdentityMap(); $fh = fopen($this->file, 'xb+'); if (count($identityMap) === 0) { fwrite($fh, 'Flush Operation [' . $this->context . "] - Empty identity map.\n"); return; } fwrite($fh, 'Flush Operation [' . $this->context . "] - Dumping identity map:\n"); foreach ($identityMap as $className => $map) { fwrite($fh, 'Class: ' . $className . "\n"); foreach ($map as $entity) { fwrite($fh, ' Entity: ' . $this->getIdString($entity, $uow) . ' ' . spl_object_id($entity) . "\n"); fwrite($fh, " Associations:\n"); $cm = $em->getClassMetadata($className); foreach ($cm->associationMappings as $field => $assoc) { fwrite($fh, ' ' . $field . ' '); $value = $cm->getFieldValue($entity, $field); if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($value === null) { fwrite($fh, " NULL\n"); } else { if ($value instanceof Proxy && ! $value->__isInitialized()) { fwrite($fh, '[PROXY] '); } fwrite($fh, $this->getIdString($value, $uow) . ' ' . spl_object_id($value) . "\n"); } } else { $initialized = ! ($value instanceof PersistentCollection) || $value->isInitialized(); if ($value === null) { fwrite($fh, " NULL\n"); } elseif ($initialized) { fwrite($fh, '[INITIALIZED] ' . $this->getType($value) . ' ' . count($value) . " elements\n"); foreach ($value as $obj) { fwrite($fh, ' ' . $this->getIdString($obj, $uow) . ' ' . spl_object_id($obj) . "\n"); } } else { fwrite($fh, '[PROXY] ' . $this->getType($value) . " unknown element size\n"); foreach ($value->unwrap() as $obj) { fwrite($fh, ' ' . $this->getIdString($obj, $uow) . ' ' . spl_object_id($obj) . "\n"); } } } } } } fclose($fh); } /** @param mixed $var */ private function getType($var): string { if (is_object($var)) { $refl = new ReflectionObject($var); return $refl->getShortName(); } return gettype($var); } /** @param object $entity */ private function getIdString($entity, UnitOfWork $uow): string { if ($uow->isInIdentityMap($entity)) { $ids = $uow->getEntityIdentifier($entity); $idstring = ''; foreach ($ids as $k => $v) { $idstring .= $k . '=' . $v; } } else { $idstring = 'NEWOBJECT '; } $state = $uow->getEntityState($entity); if ($state === UnitOfWork::STATE_NEW) { $idstring .= ' [NEW]'; } elseif ($state === UnitOfWork::STATE_REMOVED) { $idstring .= ' [REMOVED]'; } elseif ($state === UnitOfWork::STATE_MANAGED) { $idstring .= ' [MANAGED]'; } elseif ($state === UnitOfWork::STATE_DETACHED) { $idstring .= ' [DETACHED]'; } return $idstring; } } orm/lib/Doctrine/ORM/Tools/DisconnectedClassMetadataFactory.php 0000644 00000001424 15120025736 0020507 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\Persistence\Mapping\StaticReflectionService; /** * The DisconnectedClassMetadataFactory is used to create ClassMetadataInfo objects * that do not require the entity class actually exist. This allows us to * load some mapping information and use it to do things like generate code * from the mapping information. * * @deprecated This class is being removed from the ORM and will be removed in 3.0. * * @link www.doctrine-project.org */ class DisconnectedClassMetadataFactory extends ClassMetadataFactory { /** @return StaticReflectionService */ public function getReflectionService() { return new StaticReflectionService(); } } orm/lib/Doctrine/ORM/Tools/EntityGenerator.php 0000644 00000170241 15120025736 0015255 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Doctrine\Deprecations\Deprecation; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\InflectorFactory; use Doctrine\ORM\Mapping\ClassMetadataInfo; use InvalidArgumentException; use ReflectionClass; use ReflectionException; use RuntimeException; use function array_filter; use function array_keys; use function array_map; use function array_merge; use function array_sum; use function array_unique; use function array_values; use function basename; use function chmod; use function class_exists; use function copy; use function count; use function dirname; use function explode; use function file_exists; use function file_get_contents; use function file_put_contents; use function implode; use function in_array; use function is_array; use function is_dir; use function is_string; use function ltrim; use function max; use function mkdir; use function sprintf; use function str_contains; use function str_repeat; use function str_replace; use function strlen; use function strrpos; use function strtolower; use function substr; use function token_get_all; use function ucfirst; use function var_export; use const DIRECTORY_SEPARATOR; use const PHP_EOL; use const PHP_VERSION_ID; use const T_CLASS; use const T_COMMENT; use const T_DOC_COMMENT; use const T_DOUBLE_COLON; use const T_FUNCTION; use const T_NAME_FULLY_QUALIFIED; use const T_NAME_QUALIFIED; use const T_NAMESPACE; use const T_NS_SEPARATOR; use const T_PRIVATE; use const T_PROTECTED; use const T_PUBLIC; use const T_STRING; use const T_VAR; use const T_WHITESPACE; /** * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances. * * [php] * $classes = $em->getClassMetadataFactory()->getAllMetadata(); * * $generator = new \Doctrine\ORM\Tools\EntityGenerator(); * $generator->setGenerateAnnotations(true); * $generator->setGenerateStubMethods(true); * $generator->setRegenerateEntityIfExists(false); * $generator->setUpdateEntityIfExists(true); * $generator->generate($classes, '/path/to/generate/entities'); * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class EntityGenerator { /** * Specifies class fields should be protected. */ public const FIELD_VISIBLE_PROTECTED = 'protected'; /** * Specifies class fields should be private. */ public const FIELD_VISIBLE_PRIVATE = 'private'; /** @var bool */ protected $backupExisting = true; /** * The extension to use for written php files. * * @var string */ protected $extension = '.php'; /** * Whether or not the current ClassMetadataInfo instance is new or old. * * @var bool */ protected $isNew = true; /** @var mixed[] */ protected $staticReflection = []; /** * Number of spaces to use for indention in generated code. * * @var int */ protected $numSpaces = 4; /** * The actual spaces to use for indention. * * @var string */ protected $spaces = ' '; /** * The class all generated entities should extend. * * @var string */ protected $classToExtend; /** * Whether or not to generation annotations. * * @var bool */ protected $generateAnnotations = false; /** @var string */ protected $annotationsPrefix = ''; /** * Whether or not to generate sub methods. * * @var bool */ protected $generateEntityStubMethods = false; /** * Whether or not to update the entity class if it exists already. * * @var bool */ protected $updateEntityIfExists = false; /** * Whether or not to re-generate entity class if it exists already. * * @var bool */ protected $regenerateEntityIfExists = false; /** * Visibility of the field * * @var string */ protected $fieldVisibility = 'private'; /** * Whether or not to make generated embeddables immutable. * * @var bool */ protected $embeddablesImmutable = false; /** * Hash-map for handle types. * * @psalm-var array<Types::*|'json_array', string> */ protected $typeAlias = [ Types::DATETIMETZ_MUTABLE => '\DateTime', Types::DATETIME_MUTABLE => '\DateTime', Types::DATE_MUTABLE => '\DateTime', Types::TIME_MUTABLE => '\DateTime', Types::OBJECT => '\stdClass', Types::INTEGER => 'int', Types::BIGINT => 'int', Types::SMALLINT => 'int', Types::TEXT => 'string', Types::BLOB => 'string', Types::DECIMAL => 'string', Types::GUID => 'string', 'json_array' => 'array', Types::JSON => 'array', Types::SIMPLE_ARRAY => 'array', Types::BOOLEAN => 'bool', ]; /** * Hash-map to handle generator types string. * * @psalm-var array<ClassMetadataInfo::GENERATOR_TYPE_*, string> */ protected static $generatorStrategyMap = [ ClassMetadataInfo::GENERATOR_TYPE_AUTO => 'AUTO', ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE => 'SEQUENCE', ClassMetadataInfo::GENERATOR_TYPE_IDENTITY => 'IDENTITY', ClassMetadataInfo::GENERATOR_TYPE_NONE => 'NONE', ClassMetadataInfo::GENERATOR_TYPE_UUID => 'UUID', ClassMetadataInfo::GENERATOR_TYPE_CUSTOM => 'CUSTOM', ]; /** * Hash-map to handle the change tracking policy string. * * @psalm-var array<ClassMetadataInfo::CHANGETRACKING_*, string> */ protected static $changeTrackingPolicyMap = [ ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT => 'DEFERRED_IMPLICIT', ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT => 'DEFERRED_EXPLICIT', ClassMetadataInfo::CHANGETRACKING_NOTIFY => 'NOTIFY', ]; /** * Hash-map to handle the inheritance type string. * * @psalm-var array<ClassMetadataInfo::INHERITANCE_TYPE_*, string> */ protected static $inheritanceTypeMap = [ ClassMetadataInfo::INHERITANCE_TYPE_NONE => 'NONE', ClassMetadataInfo::INHERITANCE_TYPE_JOINED => 'JOINED', ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE => 'SINGLE_TABLE', ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS', ]; /** @var string */ protected static $classTemplate = '<?php <namespace> <useStatement> <entityAnnotation> <entityClassName> { <entityBody> } '; /** @var string */ protected static $getMethodTemplate = '/** * <description> * * @return <variableType> */ public function <methodName>() { <spaces>return $this-><fieldName>; }'; /** @var string */ protected static $setMethodTemplate = '/** * <description> * * @param <variableType> $<variableName> * * @return <entity> */ public function <methodName>(<methodTypeHint>$<variableName><variableDefault>) { <spaces>$this-><fieldName> = $<variableName>; <spaces>return $this; }'; /** @var string */ protected static $addMethodTemplate = '/** * <description> * * @param <variableType> $<variableName> * * @return <entity> */ public function <methodName>(<methodTypeHint>$<variableName>) { <spaces>$this-><fieldName>[] = $<variableName>; <spaces>return $this; }'; /** @var string */ protected static $removeMethodTemplate = '/** * <description> * * @param <variableType> $<variableName> * * @return boolean TRUE if this collection contained the specified element, FALSE otherwise. */ public function <methodName>(<methodTypeHint>$<variableName>) { <spaces>return $this-><fieldName>->removeElement($<variableName>); }'; /** @var string */ protected static $lifecycleCallbackMethodTemplate = '/** * @<name> */ public function <methodName>() { <spaces>// Add your code here }'; /** @var string */ protected static $constructorMethodTemplate = '/** * Constructor */ public function __construct() { <spaces><collections> } '; /** @var string */ protected static $embeddableConstructorMethodTemplate = '/** * Constructor * * <paramTags> */ public function __construct(<params>) { <spaces><fields> } '; /** @var Inflector */ protected $inflector; public function __construct() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8458', '%s is deprecated with no replacement', self::class ); $this->annotationsPrefix = 'ORM\\'; $this->inflector = InflectorFactory::create()->build(); } /** * Generates and writes entity classes for the given array of ClassMetadataInfo instances. * * @param string $outputDirectory * @psalm-param list<ClassMetadataInfo> $metadatas * * @return void */ public function generate(array $metadatas, $outputDirectory) { foreach ($metadatas as $metadata) { $this->writeEntityClass($metadata, $outputDirectory); } } /** * Generates and writes entity class to disk for the given ClassMetadataInfo instance. * * @param string $outputDirectory * * @return void * * @throws RuntimeException */ public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) { $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension; $dir = dirname($path); if (! is_dir($dir)) { mkdir($dir, 0775, true); } $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists; if (! $this->isNew) { $this->parseTokensInEntityFile(file_get_contents($path)); } else { $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []]; } if ($this->backupExisting && file_exists($path)) { $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . '~'; if (! copy($path, $backupPath)) { throw new RuntimeException('Attempt to backup overwritten entity file but copy operation failed.'); } } // If entity doesn't exist or we're re-generating the entities entirely if ($this->isNew) { file_put_contents($path, $this->generateEntityClass($metadata)); // If entity exists and we're allowed to update the entity class } elseif ($this->updateEntityIfExists) { file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path)); } chmod($path, 0664); } /** * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance. * * @return string */ public function generateEntityClass(ClassMetadataInfo $metadata) { $placeHolders = [ '<namespace>', '<useStatement>', '<entityAnnotation>', '<entityClassName>', '<entityBody>', ]; $replacements = [ $this->generateEntityNamespace($metadata), $this->generateEntityUse(), $this->generateEntityDocBlock($metadata), $this->generateEntityClassName($metadata), $this->generateEntityBody($metadata), ]; $code = str_replace($placeHolders, $replacements, static::$classTemplate); return str_replace('<spaces>', $this->spaces, $code); } /** * Generates the updated code for the given ClassMetadataInfo and entity at path. * * @param string $path * * @return string */ public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path) { $currentCode = file_get_contents($path); $body = $this->generateEntityBody($metadata); $body = str_replace('<spaces>', $this->spaces, $body); $last = strrpos($currentCode, '}'); return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n"; } /** * Sets the number of spaces the exported class should have. * * @param int $numSpaces * * @return void */ public function setNumSpaces($numSpaces) { $this->spaces = str_repeat(' ', $numSpaces); $this->numSpaces = $numSpaces; } /** * Sets the extension to use when writing php files to disk. * * @param string $extension * * @return void */ public function setExtension($extension) { $this->extension = $extension; } /** * Sets the name of the class the generated classes should extend from. * * @param string $classToExtend * * @return void */ public function setClassToExtend($classToExtend) { $this->classToExtend = $classToExtend; } /** * Sets whether or not to generate annotations for the entity. * * @param bool $bool * * @return void */ public function setGenerateAnnotations($bool) { $this->generateAnnotations = $bool; } /** * Sets the class fields visibility for the entity (can either be private or protected). * * @param string $visibility * * @return void * * @throws InvalidArgumentException * * @psalm-assert self::FIELD_VISIBLE_* $visibility */ public function setFieldVisibility($visibility) { if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) { throw new InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility); } $this->fieldVisibility = $visibility; } /** * Sets whether or not to generate immutable embeddables. * * @param bool $embeddablesImmutable * * @return void */ public function setEmbeddablesImmutable($embeddablesImmutable) { $this->embeddablesImmutable = (bool) $embeddablesImmutable; } /** * Sets an annotation prefix. * * @param string $prefix * * @return void */ public function setAnnotationPrefix($prefix) { $this->annotationsPrefix = $prefix; } /** * Sets whether or not to try and update the entity if it already exists. * * @param bool $bool * * @return void */ public function setUpdateEntityIfExists($bool) { $this->updateEntityIfExists = $bool; } /** * Sets whether or not to regenerate the entity if it exists. * * @param bool $bool * * @return void */ public function setRegenerateEntityIfExists($bool) { $this->regenerateEntityIfExists = $bool; } /** * Sets whether or not to generate stub methods for the entity. * * @param bool $bool * * @return void */ public function setGenerateStubMethods($bool) { $this->generateEntityStubMethods = $bool; } /** * Should an existing entity be backed up if it already exists? * * @param bool $bool * * @return void */ public function setBackupExisting($bool) { $this->backupExisting = $bool; } public function setInflector(Inflector $inflector): void { $this->inflector = $inflector; } /** * @param string $type * * @return string */ protected function getType($type) { if (isset($this->typeAlias[$type])) { return $this->typeAlias[$type]; } return $type; } /** @return string */ protected function generateEntityNamespace(ClassMetadataInfo $metadata) { if (! $this->hasNamespace($metadata)) { return ''; } return 'namespace ' . $this->getNamespace($metadata) . ';'; } /** @return string */ protected function generateEntityUse() { if (! $this->generateAnnotations) { return ''; } return "\n" . 'use Doctrine\ORM\Mapping as ORM;' . "\n"; } /** @return string */ protected function generateEntityClassName(ClassMetadataInfo $metadata) { return 'class ' . $this->getClassName($metadata) . ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null); } /** @return string */ protected function generateEntityBody(ClassMetadataInfo $metadata) { $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata); $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata); $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata); $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null; $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata); $code = []; if ($fieldMappingProperties) { $code[] = $fieldMappingProperties; } if ($embeddedProperties) { $code[] = $embeddedProperties; } if ($associationMappingProperties) { $code[] = $associationMappingProperties; } $code[] = $this->generateEntityConstructor($metadata); if ($stubMethods) { $code[] = $stubMethods; } if ($lifecycleCallbackMethods) { $code[] = $lifecycleCallbackMethods; } return implode("\n", $code); } /** @return string */ protected function generateEntityConstructor(ClassMetadataInfo $metadata) { if ($this->hasMethod('__construct', $metadata)) { return ''; } if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) { return $this->generateEmbeddableConstructor($metadata); } $collections = []; foreach ($metadata->associationMappings as $mapping) { if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { $collections[] = '$this->' . $mapping['fieldName'] . ' = new \Doctrine\Common\Collections\ArrayCollection();'; } } if ($collections) { return $this->prefixCodeWithSpaces(str_replace('<collections>', implode("\n" . $this->spaces, $collections), static::$constructorMethodTemplate)); } return ''; } private function generateEmbeddableConstructor(ClassMetadataInfo $metadata): string { $paramTypes = []; $paramVariables = []; $params = []; $fields = []; // Resort fields to put optional fields at the end of the method signature. $requiredFields = []; $optionalFields = []; foreach ($metadata->fieldMappings as $fieldMapping) { if (empty($fieldMapping['nullable'])) { $requiredFields[] = $fieldMapping; continue; } $optionalFields[] = $fieldMapping; } $fieldMappings = array_merge($requiredFields, $optionalFields); foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { $paramType = '\\' . ltrim($embeddedClass['class'], '\\'); $paramVariable = '$' . $fieldName; $paramTypes[] = $paramType; $paramVariables[] = $paramVariable; $params[] = $paramType . ' ' . $paramVariable; $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';'; } foreach ($fieldMappings as $fieldMapping) { if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) { continue; } $paramTypes[] = $this->getType($fieldMapping['type']) . (! empty($fieldMapping['nullable']) ? '|null' : ''); $param = '$' . $fieldMapping['fieldName']; $paramVariables[] = $param; if ($fieldMapping['type'] === 'datetime') { $param = $this->getType($fieldMapping['type']) . ' ' . $param; } if (! empty($fieldMapping['nullable'])) { $param .= ' = null'; } $params[] = $param; $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';'; } $maxParamTypeLength = max(array_map('strlen', $paramTypes)); $paramTags = array_map( static function ($type, $variable) use ($maxParamTypeLength) { return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable; }, $paramTypes, $paramVariables ); // Generate multi line constructor if the signature exceeds 120 characters. if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) { $delimiter = "\n" . $this->spaces; $params = $delimiter . implode(',' . $delimiter, $params) . "\n"; } else { $params = implode(', ', $params); } $replacements = [ '<paramTags>' => implode("\n * ", $paramTags), '<params>' => $params, '<fields>' => implode("\n" . $this->spaces, $fields), ]; $constructor = str_replace( array_keys($replacements), array_values($replacements), static::$embeddableConstructorMethodTemplate ); return $this->prefixCodeWithSpaces($constructor); } /** * @param string $src * * @return void * * @todo this won't work if there is a namespace in brackets and a class outside of it. * @psalm-suppress UndefinedConstant */ protected function parseTokensInEntityFile($src) { $tokens = token_get_all($src); $tokensCount = count($tokens); $lastSeenNamespace = ''; $lastSeenClass = false; $inNamespace = false; $inClass = false; for ($i = 0; $i < $tokensCount; $i++) { $token = $tokens[$i]; if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) { continue; } if ($inNamespace) { if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) { $lastSeenNamespace .= $token[1]; } elseif (PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)) { $lastSeenNamespace .= $token[1]; } elseif (is_string($token) && in_array($token, [';', '{'], true)) { $inNamespace = false; } } if ($inClass) { $inClass = false; $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1]; $this->staticReflection[$lastSeenClass]['properties'] = []; $this->staticReflection[$lastSeenClass]['methods'] = []; } if ($token[0] === T_NAMESPACE) { $lastSeenNamespace = ''; $inNamespace = true; } elseif ($token[0] === T_CLASS && $tokens[$i - 1][0] !== T_DOUBLE_COLON) { $inClass = true; } elseif ($token[0] === T_FUNCTION) { if ($tokens[$i + 2][0] === T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i + 2][1]); } elseif ($tokens[$i + 2] === '&' && $tokens[$i + 3][0] === T_STRING) { $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i + 3][1]); } } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && $tokens[$i + 2][0] !== T_FUNCTION) { $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i + 2][1], 1); } } } /** * @param string $property * * @return bool */ protected function hasProperty($property, ClassMetadataInfo $metadata) { if ($this->extendsClass() || (! $this->isNew && class_exists($metadata->name))) { // don't generate property if its already on the base class. $reflClass = new ReflectionClass($this->getClassToExtend() ?: $metadata->name); if ($reflClass->hasProperty($property)) { return true; } } // check traits for existing property foreach ($this->getTraits($metadata) as $trait) { if ($trait->hasProperty($property)) { return true; } } return isset($this->staticReflection[$metadata->name]) && in_array($property, $this->staticReflection[$metadata->name]['properties'], true); } /** * @param string $method * * @return bool */ protected function hasMethod($method, ClassMetadataInfo $metadata) { if ($this->extendsClass() || (! $this->isNew && class_exists($metadata->name))) { // don't generate method if its already on the base class. $reflClass = new ReflectionClass($this->getClassToExtend() ?: $metadata->name); if ($reflClass->hasMethod($method)) { return true; } } // check traits for existing method foreach ($this->getTraits($metadata) as $trait) { if ($trait->hasMethod($method)) { return true; } } return isset($this->staticReflection[$metadata->name]) && in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true); } /** * @return ReflectionClass[] * @psalm-return array<trait-string, ReflectionClass<object>> * * @throws ReflectionException */ protected function getTraits(ClassMetadataInfo $metadata) { if (! ($metadata->reflClass !== null || class_exists($metadata->name))) { return []; } $reflClass = $metadata->reflClass ?? new ReflectionClass($metadata->name); $traits = []; while ($reflClass !== false) { $traits = array_merge($traits, $reflClass->getTraits()); $reflClass = $reflClass->getParentClass(); } return $traits; } /** @return bool */ protected function hasNamespace(ClassMetadataInfo $metadata) { return str_contains($metadata->name, '\\'); } /** @return bool */ protected function extendsClass() { return (bool) $this->classToExtend; } /** @return string */ protected function getClassToExtend() { return $this->classToExtend; } /** @return string */ protected function getClassToExtendName() { $refl = new ReflectionClass($this->getClassToExtend()); return '\\' . $refl->getName(); } /** @return string */ protected function getClassName(ClassMetadataInfo $metadata) { return ($pos = strrpos($metadata->name, '\\')) ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name; } /** @return string */ protected function getNamespace(ClassMetadataInfo $metadata) { return substr($metadata->name, 0, strrpos($metadata->name, '\\')); } /** @return string */ protected function generateEntityDocBlock(ClassMetadataInfo $metadata) { $lines = []; $lines[] = '/**'; $lines[] = ' * ' . $this->getClassName($metadata); if ($this->generateAnnotations) { $lines[] = ' *'; $methods = [ 'generateTableAnnotation', 'generateInheritanceAnnotation', 'generateDiscriminatorColumnAnnotation', 'generateDiscriminatorMapAnnotation', 'generateEntityAnnotation', 'generateEntityListenerAnnotation', ]; foreach ($methods as $method) { $code = $this->$method($metadata); if ($code) { $lines[] = ' * ' . $code; } } if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks'; } } $lines[] = ' */'; return implode("\n", $lines); } /** @return string */ protected function generateEntityAnnotation(ClassMetadataInfo $metadata) { $prefix = '@' . $this->annotationsPrefix; if ($metadata->isEmbeddedClass) { return $prefix . 'Embeddable'; } $customRepository = $metadata->customRepositoryClassName ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")' : ''; return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository; } /** @return string */ protected function generateTableAnnotation(ClassMetadataInfo $metadata) { if ($metadata->isEmbeddedClass) { return ''; } $table = []; if (isset($metadata->table['schema'])) { $table[] = 'schema="' . $metadata->table['schema'] . '"'; } if (isset($metadata->table['name'])) { $table[] = 'name="' . $metadata->table['name'] . '"'; } if (isset($metadata->table['options']) && $metadata->table['options']) { $table[] = 'options={' . $this->exportTableOptions($metadata->table['options']) . '}'; } if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) { $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']); $table[] = 'uniqueConstraints={' . $constraints . '}'; } if (isset($metadata->table['indexes']) && $metadata->table['indexes']) { $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']); $table[] = 'indexes={' . $constraints . '}'; } return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')'; } /** * @param string $constraintName * @psalm-param array<string, array<string, mixed>> $constraints * * @return string */ protected function generateTableConstraints($constraintName, array $constraints) { $annotations = []; foreach ($constraints as $name => $constraint) { $columns = []; foreach ($constraint['columns'] as $column) { $columns[] = '"' . $column . '"'; } $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})'; } return implode(', ', $annotations); } /** @return string */ protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata) { if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { return ''; } return '@' . $this->annotationsPrefix . 'InheritanceType("' . $this->getInheritanceTypeString($metadata->inheritanceType) . '")'; } /** @return string */ protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata) { if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { return ''; } $discrColumn = $metadata->discriminatorColumn; if ($discrColumn === null) { return ''; } $columnDefinition = sprintf('name="%s", type="%s"', $discrColumn['name'], $discrColumn['type']); if (isset($discrColumn['length'])) { $columnDefinition .= ', length=' . $discrColumn['length']; } return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; } /** @return string|null */ protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata) { if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { return null; } $inheritanceClassMap = []; foreach ($metadata->discriminatorMap as $type => $class) { $inheritanceClassMap[] = '"' . $type . '" = "' . $class . '"'; } return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; } /** @return string */ protected function generateEntityStubMethods(ClassMetadataInfo $metadata) { $methods = []; foreach ($metadata->fieldMappings as $fieldMapping) { if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) { continue; } $nullableField = $this->nullableFieldExpression($fieldMapping); if ( (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) && (! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE) ) { $methods[] = $this->generateEntityStubMethod( $metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField ); } $methods[] = $this->generateEntityStubMethod( $metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField ); } foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { if (isset($embeddedClass['declaredField'])) { continue; } if (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) { $methods[] = $this->generateEntityStubMethod( $metadata, 'set', $fieldName, $embeddedClass['class'] ); } $methods[] = $this->generateEntityStubMethod( $metadata, 'get', $fieldName, $embeddedClass['class'] ); } foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null; $methods[] = $this->generateEntityStubMethod( $metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable ); $methods[] = $this->generateEntityStubMethod( $metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable ); } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { $methods[] = $this->generateEntityStubMethod( $metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'] ); $methods[] = $this->generateEntityStubMethod( $metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'] ); $methods[] = $this->generateEntityStubMethod( $metadata, 'get', $associationMapping['fieldName'], Collection::class ); } } return implode("\n\n", array_filter($methods)); } /** * @psalm-param array<string, mixed> $associationMapping * * @return bool */ protected function isAssociationIsNullable(array $associationMapping) { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; } if (isset($associationMapping['joinColumns'])) { $joinColumns = $associationMapping['joinColumns']; } else { //@todo there is no way to retrieve targetEntity metadata $joinColumns = []; } foreach ($joinColumns as $joinColumn) { if (isset($joinColumn['nullable']) && ! $joinColumn['nullable']) { return false; } } return true; } /** @return string */ protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) { if (empty($metadata->lifecycleCallbacks)) { return ''; } $methods = []; foreach ($metadata->lifecycleCallbacks as $name => $callbacks) { foreach ($callbacks as $callback) { $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata); } } return implode("\n\n", array_filter($methods)); } /** @return string */ protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata) { $lines = []; foreach ($metadata->associationMappings as $associationMapping) { if ($this->hasProperty($associationMapping['fieldName'], $metadata)) { continue; } $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName'] . ($associationMapping['type'] === ClassMetadataInfo::MANY_TO_MANY ? ' = array()' : null) . ";\n"; } return implode("\n", $lines); } /** @return string */ protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata) { $lines = []; foreach ($metadata->fieldMappings as $fieldMapping) { if ( isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) || $this->hasProperty($fieldMapping['fieldName'], $metadata) || $metadata->isInheritedField($fieldMapping['fieldName']) ) { continue; } $defaultValue = ''; if (isset($fieldMapping['options']['default'])) { if ($fieldMapping['type'] === 'boolean' && $fieldMapping['options']['default'] === '1') { $defaultValue = ' = true'; } elseif (($fieldMapping['type'] === 'integer' || $fieldMapping['type'] === 'float') && ! empty($fieldMapping['options']['default'])) { $defaultValue = ' = ' . (string) $fieldMapping['options']['default']; } else { $defaultValue = ' = ' . var_export($fieldMapping['options']['default'], true); } } $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . $defaultValue . ";\n"; } return implode("\n", $lines); } /** @return string */ protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata) { $lines = []; foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) { continue; } $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass); $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n"; } return implode("\n", $lines); } /** * @param string $type * @param string $fieldName * @param string|null $typeHint * @param string|null $defaultValue * * @return string */ protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) { $methodName = $type . $this->inflector->classify($fieldName); $variableName = $this->inflector->camelize($fieldName); if (in_array($type, ['add', 'remove'], true)) { $methodName = $this->inflector->singularize($methodName); $variableName = $this->inflector->singularize($variableName); } if ($this->hasMethod($methodName, $metadata)) { return ''; } $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName); $var = sprintf('%sMethodTemplate', $type); $template = static::$$var; $methodTypeHint = ''; $types = Type::getTypesMap(); $variableType = $typeHint ? $this->getType($typeHint) : null; if ($typeHint && ! isset($types[$typeHint])) { $variableType = '\\' . ltrim($variableType, '\\'); $methodTypeHint = '\\' . $typeHint . ' '; } $replacements = [ '<description>' => ucfirst($type) . ' ' . $variableName . '.', '<methodTypeHint>' => $methodTypeHint, '<variableType>' => $variableType . ($defaultValue !== null ? '|' . $defaultValue : ''), '<variableName>' => $variableName, '<methodName>' => $methodName, '<fieldName>' => $fieldName, '<variableDefault>' => $defaultValue !== null ? ' = ' . $defaultValue : '', '<entity>' => $this->getClassName($metadata), ]; $method = str_replace( array_keys($replacements), array_values($replacements), $template ); return $this->prefixCodeWithSpaces($method); } /** * @param string $name * @param string $methodName * * @return string */ protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata) { if ($this->hasMethod($methodName, $metadata)) { return ''; } $this->staticReflection[$metadata->name]['methods'][] = $methodName; $replacements = [ '<name>' => $this->annotationsPrefix . ucfirst($name), '<methodName>' => $methodName, ]; $method = str_replace( array_keys($replacements), array_values($replacements), static::$lifecycleCallbackMethodTemplate ); return $this->prefixCodeWithSpaces($method); } /** * @psalm-param array<string, mixed> $joinColumn * * @return string */ protected function generateJoinColumnAnnotation(array $joinColumn) { $joinColumnAnnot = []; if (isset($joinColumn['name'])) { $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"'; } if (isset($joinColumn['referencedColumnName'])) { $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"'; } if (isset($joinColumn['unique']) && $joinColumn['unique']) { $joinColumnAnnot[] = 'unique=true'; } if (isset($joinColumn['nullable'])) { $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false'); } if (isset($joinColumn['onDelete'])) { $joinColumnAnnot[] = 'onDelete="' . $joinColumn['onDelete'] . '"'; } if (isset($joinColumn['columnDefinition'])) { $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"'; } return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')'; } /** * @param mixed[] $associationMapping * * @return string */ protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata) { $lines = []; $lines[] = $this->spaces . '/**'; if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection'; } else { $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\'); } if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; if (isset($associationMapping['id']) && $associationMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; $generatorType = $this->getIdGeneratorTypeString($metadata->generatorType); if ($generatorType) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } } $type = null; switch ($associationMapping['type']) { case ClassMetadataInfo::ONE_TO_ONE: $type = 'OneToOne'; break; case ClassMetadataInfo::MANY_TO_ONE: $type = 'ManyToOne'; break; case ClassMetadataInfo::ONE_TO_MANY: $type = 'OneToMany'; break; case ClassMetadataInfo::MANY_TO_MANY: $type = 'ManyToMany'; break; } $typeOptions = []; if (isset($associationMapping['targetEntity'])) { $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"'; } if (isset($associationMapping['inversedBy'])) { $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"'; } if (isset($associationMapping['mappedBy'])) { $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"'; } if ($associationMapping['cascade']) { $cascades = []; if ($associationMapping['isCascadePersist']) { $cascades[] = '"persist"'; } if ($associationMapping['isCascadeRemove']) { $cascades[] = '"remove"'; } if ($associationMapping['isCascadeDetach']) { $cascades[] = '"detach"'; } if ($associationMapping['isCascadeMerge']) { $cascades[] = '"merge"'; } if ($associationMapping['isCascadeRefresh']) { $cascades[] = '"refresh"'; } if (count($cascades) === 5) { $cascades = ['"all"']; } $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) { $typeOptions[] = 'orphanRemoval=true'; } if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) { $fetchMap = [ ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY', ClassMetadataInfo::FETCH_EAGER => 'EAGER', ]; $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"'; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')'; if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({'; $joinColumnsLines = []; foreach ($associationMapping['joinColumns'] as $joinColumn) { $joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn); if ($joinColumnAnnot) { $joinColumnsLines[] = $this->spaces . ' * ' . $joinColumnAnnot; } } $lines[] = implode(",\n", $joinColumnsLines); $lines[] = $this->spaces . ' * })'; } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTable = []; $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"'; if (isset($associationMapping['joinTable']['schema'])) { $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"'; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ','; $lines[] = $this->spaces . ' * joinColumns={'; $joinColumnsLines = []; foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { $joinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(',' . PHP_EOL, $joinColumnsLines); $lines[] = $this->spaces . ' * },'; $lines[] = $this->spaces . ' * inverseJoinColumns={'; $inverseJoinColumnsLines = []; foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) { $inverseJoinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); } $lines[] = implode(',' . PHP_EOL, $inverseJoinColumnsLines); $lines[] = $this->spaces . ' * }'; $lines[] = $this->spaces . ' * )'; } if (isset($associationMapping['orderBy'])) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({'; foreach ($associationMapping['orderBy'] as $name => $direction) { $lines[] = $this->spaces . ' * "' . $name . '"="' . $direction . '",'; } $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1); $lines[] = $this->spaces . ' * })'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } /** * @param mixed[] $fieldMapping * * @return string */ protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) { $lines = []; $lines[] = $this->spaces . '/**'; $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']) . ($this->nullableFieldExpression($fieldMapping) ? '|null' : ''); if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; $column = []; if (isset($fieldMapping['columnName'])) { $column[] = 'name="' . $fieldMapping['columnName'] . '"'; } if (isset($fieldMapping['type'])) { $column[] = 'type="' . $fieldMapping['type'] . '"'; } if (isset($fieldMapping['length'])) { $column[] = 'length=' . $fieldMapping['length']; } if (isset($fieldMapping['precision'])) { $column[] = 'precision=' . $fieldMapping['precision']; } if (isset($fieldMapping['scale'])) { $column[] = 'scale=' . $fieldMapping['scale']; } if (isset($fieldMapping['nullable'])) { $column[] = 'nullable=' . var_export($fieldMapping['nullable'], true); } $options = []; if (isset($fieldMapping['options']['default']) && $fieldMapping['options']['default']) { $options[] = '"default"="' . $fieldMapping['options']['default'] . '"'; } if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) { $options[] = '"unsigned"=true'; } if (isset($fieldMapping['options']['fixed']) && $fieldMapping['options']['fixed']) { $options[] = '"fixed"=true'; } if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) { $options[] = '"comment"="' . str_replace('"', '""', $fieldMapping['options']['comment']) . '"'; } if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) { $options[] = '"collation"="' . $fieldMapping['options']['collation'] . '"'; } if (isset($fieldMapping['options']['check']) && $fieldMapping['options']['check']) { $options[] = '"check"="' . $fieldMapping['options']['check'] . '"'; } if ($options) { $column[] = 'options={' . implode(',', $options) . '}'; } if (isset($fieldMapping['columnDefinition'])) { $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"'; } if (isset($fieldMapping['unique'])) { $column[] = 'unique=' . var_export($fieldMapping['unique'], true); } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')'; if (isset($fieldMapping['id']) && $fieldMapping['id']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; $generatorType = $this->getIdGeneratorTypeString($metadata->generatorType); if ($generatorType) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; } if ($metadata->sequenceGeneratorDefinition) { $sequenceGenerator = []; if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) { $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"'; } if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) { $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize']; } if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) { $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue']; } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')'; } } if (isset($fieldMapping['version']) && $fieldMapping['version']) { $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version'; } } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } /** * @psalm-param array<string, mixed> $embeddedClass * * @return string */ protected function generateEmbeddedPropertyDocBlock(array $embeddedClass) { $lines = []; $lines[] = $this->spaces . '/**'; $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\'); if ($this->generateAnnotations) { $lines[] = $this->spaces . ' *'; $embedded = ['class="' . $embeddedClass['class'] . '"']; if (isset($embeddedClass['columnPrefix'])) { if (is_string($embeddedClass['columnPrefix'])) { $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"'; } else { $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true); } } $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')'; } $lines[] = $this->spaces . ' */'; return implode("\n", $lines); } private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string { if (count($metadata->entityListeners) === 0) { return ''; } $processedClasses = []; foreach ($metadata->entityListeners as $event => $eventListeners) { foreach ($eventListeners as $eventListener) { $processedClasses[] = '"' . $eventListener['class'] . '"'; } } return sprintf( '%s%s({%s})', '@' . $this->annotationsPrefix, 'EntityListeners', implode(',', array_unique($processedClasses)) ); } /** * @param string $code * @param int $num * * @return string */ protected function prefixCodeWithSpaces($code, $num = 1) { $lines = explode("\n", $code); foreach ($lines as $key => $value) { if ($value !== '') { $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key]; } } return implode("\n", $lines); } /** * @param int $type The inheritance type used by the class and its subclasses. * * @return string The literal string for the inheritance type. * * @throws InvalidArgumentException When the inheritance type does not exist. */ protected function getInheritanceTypeString($type) { if (! isset(static::$inheritanceTypeMap[$type])) { throw new InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type)); } return static::$inheritanceTypeMap[$type]; } /** * @param int $type The policy used for change-tracking for the mapped class. * * @return string The literal string for the change-tracking type. * * @throws InvalidArgumentException When the change-tracking type does not exist. */ protected function getChangeTrackingPolicyString($type) { if (! isset(static::$changeTrackingPolicyMap[$type])) { throw new InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type)); } return static::$changeTrackingPolicyMap[$type]; } /** * @param int $type The generator to use for the mapped class. * * @return string The literal string for the generator type. * * @throws InvalidArgumentException When the generator type does not exist. */ protected function getIdGeneratorTypeString($type) { if (! isset(static::$generatorStrategyMap[$type])) { throw new InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type)); } return static::$generatorStrategyMap[$type]; } /** @psalm-param array<string, mixed> $fieldMapping */ private function nullableFieldExpression(array $fieldMapping): ?string { if (isset($fieldMapping['nullable']) && $fieldMapping['nullable'] === true) { return 'null'; } return null; } /** * Exports (nested) option elements. * * @psalm-param array<string, mixed> $options */ private function exportTableOptions(array $options): string { $optionsStr = []; foreach ($options as $name => $option) { if (is_array($option)) { $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}'; } else { $optionsStr[] = '"' . $name . '"="' . (string) $option . '"'; } } return implode(',', $optionsStr); } } orm/lib/Doctrine/ORM/Tools/EntityRepositoryGenerator.php 0000644 00000010334 15120025736 0017351 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityRepository; use function array_keys; use function array_values; use function chmod; use function dirname; use function file_exists; use function file_put_contents; use function is_dir; use function mkdir; use function str_replace; use function strlen; use function strrpos; use function substr; use const DIRECTORY_SEPARATOR; /** * Class to generate entity repository classes * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class EntityRepositoryGenerator { /** @psalm-var class-string|null */ private $repositoryName; /** @var string */ protected static $_template = '<?php <namespace> /** * <className> * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class <className> extends <repositoryName> { } '; public function __construct() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8458', '%s is deprecated and will be removed in Doctrine ORM 3.0', self::class ); } /** * @param string $fullClassName * * @return string */ public function generateEntityRepositoryClass($fullClassName) { $variables = [ '<namespace>' => $this->generateEntityRepositoryNamespace($fullClassName), '<repositoryName>' => $this->generateEntityRepositoryName($fullClassName), '<className>' => $this->generateClassName($fullClassName), ]; return str_replace(array_keys($variables), array_values($variables), self::$_template); } /** * Generates the namespace, if class do not have namespace, return empty string instead. * * @psalm-param class-string $fullClassName */ private function getClassNamespace(string $fullClassName): string { return substr($fullClassName, 0, (int) strrpos($fullClassName, '\\')); } /** * Generates the class name * * @psalm-param class-string $fullClassName */ private function generateClassName(string $fullClassName): string { $namespace = $this->getClassNamespace($fullClassName); $className = $fullClassName; if ($namespace) { $className = substr($fullClassName, strrpos($fullClassName, '\\') + 1, strlen($fullClassName)); } return $className; } /** * Generates the namespace statement, if class do not have namespace, return empty string instead. * * @psalm-param class-string $fullClassName The full repository class name. */ private function generateEntityRepositoryNamespace(string $fullClassName): string { $namespace = $this->getClassNamespace($fullClassName); return $namespace ? 'namespace ' . $namespace . ';' : ''; } private function generateEntityRepositoryName(string $fullClassName): string { $namespace = $this->getClassNamespace($fullClassName); $repositoryName = $this->repositoryName ?: EntityRepository::class; if ($namespace && $repositoryName[0] !== '\\') { $repositoryName = '\\' . $repositoryName; } return $repositoryName; } /** * @param string $fullClassName * @param string $outputDirectory * * @return void */ public function writeEntityRepositoryClass($fullClassName, $outputDirectory) { $code = $this->generateEntityRepositoryClass($fullClassName); $path = $outputDirectory . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $fullClassName) . '.php'; $dir = dirname($path); if (! is_dir($dir)) { mkdir($dir, 0775, true); } if (! file_exists($path)) { file_put_contents($path, $code); chmod($path, 0664); } } /** * @param string $repositoryName * * @return $this */ public function setDefaultRepositoryName($repositoryName) { $this->repositoryName = $repositoryName; return $this; } } orm/lib/Doctrine/ORM/Tools/ResolveTargetEntityListener.php 0000644 00000007701 15120025736 0017623 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; use function array_key_exists; use function array_replace_recursive; use function ltrim; /** * ResolveTargetEntityListener * * Mechanism to overwrite interfaces or classes specified as association * targets. * * @psalm-import-type AssociationMapping from ClassMetadata */ class ResolveTargetEntityListener implements EventSubscriber { /** @var mixed[][] indexed by original entity name */ private $resolveTargetEntities = []; /** * {@inheritDoc} */ public function getSubscribedEvents() { return [ Events::loadClassMetadata, Events::onClassMetadataNotFound, ]; } /** * Adds a target-entity class name to resolve to a new class name. * * @param string $originalEntity * @param string $newEntity * @psalm-param array<string, mixed> $mapping * * @return void */ public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping) { $mapping['targetEntity'] = ltrim($newEntity, '\\'); $this->resolveTargetEntities[ltrim($originalEntity, '\\')] = $mapping; } /** * @internal this is an event callback, and should not be called directly * * @return void */ public function onClassMetadataNotFound(OnClassMetadataNotFoundEventArgs $args) { if (array_key_exists($args->getClassName(), $this->resolveTargetEntities)) { $args->setFoundMetadata( $args ->getObjectManager() ->getClassMetadata($this->resolveTargetEntities[$args->getClassName()]['targetEntity']) ); } } /** * Processes event and resolves new target entity names. * * @internal this is an event callback, and should not be called directly * * @return void */ public function loadClassMetadata(LoadClassMetadataEventArgs $args) { $cm = $args->getClassMetadata(); foreach ($cm->associationMappings as $mapping) { if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) { $this->remapAssociation($cm, $mapping); } } foreach ($this->resolveTargetEntities as $interface => $data) { if ($data['targetEntity'] === $cm->getName()) { $args->getEntityManager()->getMetadataFactory()->setMetadataFor($interface, $cm); } } foreach ($cm->discriminatorMap as $value => $class) { if (isset($this->resolveTargetEntities[$class])) { $cm->addDiscriminatorMapClass($value, $this->resolveTargetEntities[$class]['targetEntity']); } } } /** @param AssociationMapping $mapping */ private function remapAssociation(ClassMetadata $classMetadata, array $mapping): void { $newMapping = $this->resolveTargetEntities[$mapping['targetEntity']]; $newMapping = array_replace_recursive($mapping, $newMapping); $newMapping['fieldName'] = $mapping['fieldName']; unset($classMetadata->associationMappings[$mapping['fieldName']]); switch ($mapping['type']) { case ClassMetadata::MANY_TO_MANY: $classMetadata->mapManyToMany($newMapping); break; case ClassMetadata::MANY_TO_ONE: $classMetadata->mapManyToOne($newMapping); break; case ClassMetadata::ONE_TO_MANY: $classMetadata->mapOneToMany($newMapping); break; case ClassMetadata::ONE_TO_ONE: $classMetadata->mapOneToOne($newMapping); break; } } } orm/lib/Doctrine/ORM/Tools/SchemaTool.php 0000644 00000114553 15120025736 0014174 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use BackedEnum; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; use Doctrine\ORM\Tools\Exception\MissingColumnException; use Doctrine\ORM\Tools\Exception\NotSupported; use Throwable; use function array_diff; use function array_diff_key; use function array_filter; use function array_flip; use function array_intersect_key; use function assert; use function count; use function current; use function func_num_args; use function implode; use function in_array; use function is_array; use function is_numeric; use function method_exists; use function strtolower; /** * The SchemaTool is a tool to create/drop/update database schemas based on * <tt>ClassMetadata</tt> class descriptors. * * @link www.doctrine-project.org * * @psalm-import-type AssociationMapping from ClassMetadata * @psalm-import-type DiscriminatorColumnMapping from ClassMetadata * @psalm-import-type FieldMapping from ClassMetadata * @psalm-import-type JoinColumnData from ClassMetadata */ class SchemaTool { private const KNOWN_COLUMN_OPTIONS = ['comment', 'unsigned', 'fixed', 'default']; /** @var EntityManagerInterface */ private $em; /** @var AbstractPlatform */ private $platform; /** * The quote strategy. * * @var QuoteStrategy */ private $quoteStrategy; /** @var AbstractSchemaManager */ private $schemaManager; /** * Initializes a new SchemaTool instance that uses the connection of the * provided EntityManager. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->platform = $em->getConnection()->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $this->schemaManager = method_exists(Connection::class, 'createSchemaManager') ? $em->getConnection()->createSchemaManager() : $em->getConnection()->getSchemaManager(); } /** * Creates the database schema for the given array of ClassMetadata instances. * * @psalm-param list<ClassMetadata> $classes * * @return void * * @throws ToolsException */ public function createSchema(array $classes) { $createSchemaSql = $this->getCreateSchemaSql($classes); $conn = $this->em->getConnection(); foreach ($createSchemaSql as $sql) { try { $conn->executeStatement($sql); } catch (Throwable $e) { throw ToolsException::schemaToolFailure($sql, $e); } } } /** * Gets the list of DDL statements that are required to create the database schema for * the given list of ClassMetadata instances. * * @psalm-param list<ClassMetadata> $classes * * @return list<string> The SQL statements needed to create the schema for the classes. */ public function getCreateSchemaSql(array $classes) { $schema = $this->getSchemaFromMetadata($classes); return $schema->toSql($this->platform); } /** * Detects instances of ClassMetadata that don't need to be processed in the SchemaTool context. * * @psalm-param array<string, bool> $processedClasses */ private function processingNotRequired( ClassMetadata $class, array $processedClasses ): bool { return isset($processedClasses[$class->name]) || $class->isMappedSuperclass || $class->isEmbeddedClass || ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName) || in_array($class->name, $this->em->getConfiguration()->getSchemaIgnoreClasses()); } /** * Resolves fields in index mapping to column names * * @param mixed[] $indexData index or unique constraint data * * @return list<string> Column names from combined fields and columns mappings */ private function getIndexColumns(ClassMetadata $class, array $indexData): array { $columns = []; if ( isset($indexData['columns'], $indexData['fields']) || ( ! isset($indexData['columns']) && ! isset($indexData['fields']) ) ) { throw MappingException::invalidIndexConfiguration( $class, $indexData['name'] ?? 'unnamed' ); } if (isset($indexData['columns'])) { $columns = $indexData['columns']; } if (isset($indexData['fields'])) { foreach ($indexData['fields'] as $fieldName) { if ($class->hasField($fieldName)) { $columns[] = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform); } elseif ($class->hasAssociation($fieldName)) { foreach ($class->getAssociationMapping($fieldName)['joinColumns'] as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } } } return $columns; } /** * Creates a Schema instance from a given set of metadata classes. * * @psalm-param list<ClassMetadata> $classes * * @return Schema * * @throws NotSupported */ public function getSchemaFromMetadata(array $classes) { // Reminder for processed classes, used for hierarchies $processedClasses = []; $eventManager = $this->em->getEventManager(); $metadataSchemaConfig = $this->schemaManager->createSchemaConfig(); $schema = new Schema([], [], $metadataSchemaConfig); $addedFks = []; $blacklistedFks = []; foreach ($classes as $class) { if ($this->processingNotRequired($class, $processedClasses)) { continue; } $table = $schema->createTable($this->quoteStrategy->getTableName($class, $this->platform)); if ($class->isInheritanceTypeSingleTable()) { $this->gatherColumns($class, $table); $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column $this->addDiscriminatorColumnDefinition($class, $table); // Aggregate all the information from all classes in the hierarchy foreach ($class->parentClasses as $parentClassName) { // Parent class information is already contained in this class $processedClasses[$parentClassName] = true; } foreach ($class->subClasses as $subClassName) { $subClass = $this->em->getClassMetadata($subClassName); $this->gatherColumns($subClass, $table); $this->gatherRelationsSql($subClass, $table, $schema, $addedFks, $blacklistedFks); $processedClasses[$subClassName] = true; } } elseif ($class->isInheritanceTypeJoined()) { // Add all non-inherited fields as columns foreach ($class->fieldMappings as $fieldName => $mapping) { if (! isset($mapping['inherited'])) { $this->gatherColumn($class, $mapping, $table); } } $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); // Add the discriminator column only to the root table if ($class->name === $class->rootEntityName) { $this->addDiscriminatorColumnDefinition($class, $table); } else { // Add an ID FK column to child tables $pkColumns = []; $inheritedKeyColumns = []; foreach ($class->identifier as $identifierField) { if (isset($class->fieldMappings[$identifierField]['inherited'])) { $idMapping = $class->fieldMappings[$identifierField]; $this->gatherColumn($class, $idMapping, $table); $columnName = $this->quoteStrategy->getColumnName( $identifierField, $class, $this->platform ); // TODO: This seems rather hackish, can we optimize it? $table->getColumn($columnName)->setAutoincrement(false); $pkColumns[] = $columnName; $inheritedKeyColumns[] = $columnName; continue; } if (isset($class->associationMappings[$identifierField]['inherited'])) { $idMapping = $class->associationMappings[$identifierField]; $targetEntity = current( array_filter( $classes, static function (ClassMetadata $class) use ($idMapping): bool { return $class->name === $idMapping['targetEntity']; } ) ); foreach ($idMapping['joinColumns'] as $joinColumn) { if (isset($targetEntity->fieldMappings[$joinColumn['referencedColumnName']])) { $columnName = $this->quoteStrategy->getJoinColumnName( $joinColumn, $class, $this->platform ); $pkColumns[] = $columnName; $inheritedKeyColumns[] = $columnName; } } } } if ($inheritedKeyColumns !== []) { // Add a FK constraint on the ID column $table->addForeignKeyConstraint( $this->quoteStrategy->getTableName( $this->em->getClassMetadata($class->rootEntityName), $this->platform ), $inheritedKeyColumns, $inheritedKeyColumns, ['onDelete' => 'CASCADE'] ); } if ($pkColumns !== []) { $table->setPrimaryKey($pkColumns); } } } elseif ($class->isInheritanceTypeTablePerClass()) { throw NotSupported::create(); } else { $this->gatherColumns($class, $table); $this->gatherRelationsSql($class, $table, $schema, $addedFks, $blacklistedFks); } $pkColumns = []; foreach ($class->identifier as $identifierField) { if (isset($class->fieldMappings[$identifierField])) { $pkColumns[] = $this->quoteStrategy->getColumnName($identifierField, $class, $this->platform); } elseif (isset($class->associationMappings[$identifierField])) { $assoc = $class->associationMappings[$identifierField]; assert(is_array($assoc)); foreach ($assoc['joinColumns'] as $joinColumn) { $pkColumns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } } if (! $table->hasIndex('primary')) { $table->setPrimaryKey($pkColumns); } // there can be unique indexes automatically created for join column // if join column is also primary key we should keep only primary key on this column // so, remove indexes overruled by primary key $primaryKey = $table->getIndex('primary'); foreach ($table->getIndexes() as $idxKey => $existingIndex) { if ($primaryKey->overrules($existingIndex)) { $table->dropIndex($idxKey); } } if (isset($class->table['indexes'])) { foreach ($class->table['indexes'] as $indexName => $indexData) { if (! isset($indexData['flags'])) { $indexData['flags'] = []; } $table->addIndex( $this->getIndexColumns($class, $indexData), is_numeric($indexName) ? null : $indexName, (array) $indexData['flags'], $indexData['options'] ?? [] ); } } if (isset($class->table['uniqueConstraints'])) { foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) { $uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []); foreach ($table->getIndexes() as $tableIndexName => $tableIndex) { $method = method_exists($tableIndex, 'isFulfilledBy') ? 'isFulfilledBy' : 'isFullfilledBy'; if ($tableIndex->$method($uniqIndex)) { $table->dropIndex($tableIndexName); break; } } $table->addUniqueIndex($uniqIndex->getColumns(), is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []); } } if (isset($class->table['options'])) { foreach ($class->table['options'] as $key => $val) { $table->addOption($key, $val); } } $processedClasses[$class->name] = true; if ($class->isIdGeneratorSequence() && $class->name === $class->rootEntityName) { $seqDef = $class->sequenceGeneratorDefinition; $quotedName = $this->quoteStrategy->getSequenceName($seqDef, $class, $this->platform); if (! $schema->hasSequence($quotedName)) { $schema->createSequence( $quotedName, (int) $seqDef['allocationSize'], (int) $seqDef['initialValue'] ); } } if ($eventManager->hasListeners(ToolEvents::postGenerateSchemaTable)) { $eventManager->dispatchEvent( ToolEvents::postGenerateSchemaTable, new GenerateSchemaTableEventArgs($class, $schema, $table) ); } } if (! $this->platform->supportsSchemas()) { $filter = /** @param Sequence|Table $asset */ static function ($asset) use ($schema): bool { return ! $asset->isInDefaultNamespace($schema->getName()); }; if (array_filter($schema->getSequences() + $schema->getTables(), $filter) && ! $this->platform->canEmulateSchemas()) { $schema->visit(new RemoveNamespacedAssets()); } } if ($eventManager->hasListeners(ToolEvents::postGenerateSchema)) { $eventManager->dispatchEvent( ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->em, $schema) ); } return $schema; } /** * Gets a portable column definition as required by the DBAL for the discriminator * column of a class. */ private function addDiscriminatorColumnDefinition(ClassMetadata $class, Table $table): void { $discrColumn = $class->discriminatorColumn; if ( ! isset($discrColumn['type']) || (strtolower($discrColumn['type']) === 'string' && ! isset($discrColumn['length'])) ) { $discrColumn['type'] = 'string'; $discrColumn['length'] = 255; } $options = [ 'length' => $discrColumn['length'] ?? null, 'notnull' => true, ]; if (isset($discrColumn['columnDefinition'])) { $options['columnDefinition'] = $discrColumn['columnDefinition']; } $options = $this->gatherColumnOptions($discrColumn) + $options; $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); } /** * Gathers the column definitions as required by the DBAL of all field mappings * found in the given class. */ private function gatherColumns(ClassMetadata $class, Table $table): void { $pkColumns = []; foreach ($class->fieldMappings as $mapping) { if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) { continue; } $this->gatherColumn($class, $mapping, $table); if ($class->isIdentifier($mapping['fieldName'])) { $pkColumns[] = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); } } } /** * Creates a column definition as required by the DBAL from an ORM field mapping definition. * * @param ClassMetadata $class The class that owns the field mapping. * @psalm-param FieldMapping $mapping The field mapping. */ private function gatherColumn( ClassMetadata $class, array $mapping, Table $table ): void { $columnName = $this->quoteStrategy->getColumnName($mapping['fieldName'], $class, $this->platform); $columnType = $mapping['type']; $options = []; $options['length'] = $mapping['length'] ?? null; $options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true; if ($class->isInheritanceTypeSingleTable() && $class->parentClasses) { $options['notnull'] = false; } $options['platformOptions'] = []; $options['platformOptions']['version'] = $class->isVersioned && $class->versionField === $mapping['fieldName']; if (strtolower($columnType) === 'string' && $options['length'] === null) { $options['length'] = 255; } if (isset($mapping['precision'])) { $options['precision'] = $mapping['precision']; } if (isset($mapping['scale'])) { $options['scale'] = $mapping['scale']; } if (isset($mapping['default'])) { $options['default'] = $mapping['default']; } if (isset($mapping['columnDefinition'])) { $options['columnDefinition'] = $mapping['columnDefinition']; } // the 'default' option can be overwritten here $options = $this->gatherColumnOptions($mapping) + $options; if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() === [$mapping['fieldName']]) { $options['autoincrement'] = true; } if ($class->isInheritanceTypeJoined() && $class->name !== $class->rootEntityName) { $options['autoincrement'] = false; } if ($table->hasColumn($columnName)) { $method = method_exists($table, 'modifyColumn') ? 'modifyColumn' : 'changeColumn'; // required in some inheritance scenarios $table->$method($columnName, $options); } else { $table->addColumn($columnName, $columnType, $options); } $isUnique = $mapping['unique'] ?? false; if ($isUnique) { $table->addUniqueIndex([$columnName]); } } /** * Gathers the SQL for properly setting up the relations of the given class. * This includes the SQL for foreign key constraints and join tables. * * @psalm-param array<string, array{ * foreignTableName: string, * foreignColumns: list<string> * }> $addedFks * @psalm-param array<string, bool> $blacklistedFks * * @throws NotSupported */ private function gatherRelationsSql( ClassMetadata $class, Table $table, Schema $schema, array &$addedFks, array &$blacklistedFks ): void { foreach ($class->associationMappings as $id => $mapping) { if (isset($mapping['inherited']) && ! in_array($id, $class->identifier, true)) { continue; } $foreignClass = $this->em->getClassMetadata($mapping['targetEntity']); if ($mapping['type'] & ClassMetadata::TO_ONE && $mapping['isOwningSide']) { $primaryKeyColumns = []; // PK is unnecessary for this relation-type $this->gatherRelationJoinColumns( $mapping['joinColumns'], $table, $foreignClass, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks ); } elseif ($mapping['type'] === ClassMetadata::ONE_TO_MANY && $mapping['isOwningSide']) { //... create join table, one-many through join table supported later throw NotSupported::create(); } elseif ($mapping['type'] === ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) { // create join table $joinTable = $mapping['joinTable']; $theJoinTable = $schema->createTable( $this->quoteStrategy->getJoinTableName($mapping, $foreignClass, $this->platform) ); if (isset($joinTable['options'])) { foreach ($joinTable['options'] as $key => $val) { $theJoinTable->addOption($key, $val); } } $primaryKeyColumns = []; // Build first FK constraint (relation table => source table) $this->gatherRelationJoinColumns( $joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks ); // Build second FK constraint (relation table => target table) $this->gatherRelationJoinColumns( $joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $addedFks, $blacklistedFks ); $theJoinTable->setPrimaryKey($primaryKeyColumns); } } } /** * Gets the class metadata that is responsible for the definition of the referenced column name. * * Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its * not a simple field, go through all identifier field names that are associations recursively and * find that referenced column name. * * TODO: Is there any way to make this code more pleasing? * * @psalm-return array{ClassMetadata, string}|null */ private function getDefiningClass(ClassMetadata $class, string $referencedColumnName): ?array { $referencedFieldName = $class->getFieldName($referencedColumnName); if ($class->hasField($referencedFieldName)) { return [$class, $referencedFieldName]; } if (in_array($referencedColumnName, $class->getIdentifierColumnNames(), true)) { // it seems to be an entity as foreign key foreach ($class->getIdentifierFieldNames() as $fieldName) { if ( $class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) === $referencedColumnName ) { return $this->getDefiningClass( $this->em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']), $class->getSingleAssociationReferencedJoinColumnName($fieldName) ); } } } return null; } /** * Gathers columns and fk constraints that are required for one part of relationship. * * @psalm-param array<string, JoinColumnData> $joinColumns * @psalm-param AssociationMapping $mapping * @psalm-param list<string> $primaryKeyColumns * @psalm-param array<string, array{ * foreignTableName: string, * foreignColumns: list<string> * }> $addedFks * @psalm-param array<string,bool> $blacklistedFks * * @throws MissingColumnException */ private function gatherRelationJoinColumns( array $joinColumns, Table $theJoinTable, ClassMetadata $class, array $mapping, array &$primaryKeyColumns, array &$addedFks, array &$blacklistedFks ): void { $localColumns = []; $foreignColumns = []; $fkOptions = []; $foreignTableName = $this->quoteStrategy->getTableName($class, $this->platform); $uniqueConstraints = []; foreach ($joinColumns as $joinColumn) { [$definingClass, $referencedFieldName] = $this->getDefiningClass( $class, $joinColumn['referencedColumnName'] ); if (! $definingClass) { throw MissingColumnException::fromColumnSourceAndTarget( $joinColumn['referencedColumnName'], $mapping['sourceEntity'], $mapping['targetEntity'] ); } $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); $quotedRefColumnName = $this->quoteStrategy->getReferencedJoinColumnName( $joinColumn, $class, $this->platform ); $primaryKeyColumns[] = $quotedColumnName; $localColumns[] = $quotedColumnName; $foreignColumns[] = $quotedRefColumnName; if (! $theJoinTable->hasColumn($quotedColumnName)) { // Only add the column to the table if it does not exist already. // It might exist already if the foreign key is mapped into a regular // property as well. $fieldMapping = $definingClass->getFieldMapping($referencedFieldName); $columnOptions = ['notnull' => false]; if (isset($joinColumn['columnDefinition'])) { $columnOptions['columnDefinition'] = $joinColumn['columnDefinition']; } elseif (isset($fieldMapping['columnDefinition'])) { $columnOptions['columnDefinition'] = $fieldMapping['columnDefinition']; } if (isset($joinColumn['nullable'])) { $columnOptions['notnull'] = ! $joinColumn['nullable']; } $columnOptions += $this->gatherColumnOptions($fieldMapping); if (isset($fieldMapping['length'])) { $columnOptions['length'] = $fieldMapping['length']; } if ($fieldMapping['type'] === 'decimal') { $columnOptions['scale'] = $fieldMapping['scale']; $columnOptions['precision'] = $fieldMapping['precision']; } $columnOptions = $this->gatherColumnOptions($joinColumn) + $columnOptions; $theJoinTable->addColumn($quotedColumnName, $fieldMapping['type'], $columnOptions); } if (isset($joinColumn['unique']) && $joinColumn['unique'] === true) { $uniqueConstraints[] = ['columns' => [$quotedColumnName]]; } if (isset($joinColumn['onDelete'])) { $fkOptions['onDelete'] = $joinColumn['onDelete']; } } // Prefer unique constraints over implicit simple indexes created for foreign keys. // Also avoids index duplication. foreach ($uniqueConstraints as $indexName => $unique) { $theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName); } $compositeName = $theJoinTable->getName() . '.' . implode('', $localColumns); if ( isset($addedFks[$compositeName]) && ($foreignTableName !== $addedFks[$compositeName]['foreignTableName'] || 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns']))) ) { foreach ($theJoinTable->getForeignKeys() as $fkName => $key) { if ( count(array_diff($key->getLocalColumns(), $localColumns)) === 0 && (($key->getForeignTableName() !== $foreignTableName) || 0 < count(array_diff($key->getForeignColumns(), $foreignColumns))) ) { $theJoinTable->removeForeignKey($fkName); break; } } $blacklistedFks[$compositeName] = true; } elseif (! isset($blacklistedFks[$compositeName])) { $addedFks[$compositeName] = ['foreignTableName' => $foreignTableName, 'foreignColumns' => $foreignColumns]; $theJoinTable->addForeignKeyConstraint( $foreignTableName, $localColumns, $foreignColumns, $fkOptions ); } } /** * @psalm-param JoinColumnData|FieldMapping|DiscriminatorColumnMapping $mapping * * @return mixed[] */ private function gatherColumnOptions(array $mapping): array { $mappingOptions = $mapping['options'] ?? []; if (isset($mapping['enumType'])) { $mappingOptions['enumType'] = $mapping['enumType']; } if (($mappingOptions['default'] ?? null) instanceof BackedEnum) { $mappingOptions['default'] = $mappingOptions['default']->value; } if (empty($mappingOptions)) { return []; } $options = array_intersect_key($mappingOptions, array_flip(self::KNOWN_COLUMN_OPTIONS)); $options['customSchemaOptions'] = array_diff_key($mappingOptions, $options); return $options; } /** * Drops the database schema for the given classes. * * In any way when an exception is thrown it is suppressed since drop was * issued for all classes of the schema and some probably just don't exist. * * @psalm-param list<ClassMetadata> $classes * * @return void */ public function dropSchema(array $classes) { $dropSchemaSql = $this->getDropSchemaSQL($classes); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { try { $conn->executeStatement($sql); } catch (Throwable $e) { // ignored } } } /** * Drops all elements in the database of the current connection. * * @return void */ public function dropDatabase() { $dropSchemaSql = $this->getDropDatabaseSQL(); $conn = $this->em->getConnection(); foreach ($dropSchemaSql as $sql) { $conn->executeStatement($sql); } } /** * Gets the SQL needed to drop the database schema for the connections database. * * @return list<string> */ public function getDropDatabaseSQL() { $method = method_exists(AbstractSchemaManager::class, 'introspectSchema') ? 'introspectSchema' : 'createSchema'; return $this->schemaManager ->$method() ->toDropSql($this->platform); } /** * Gets SQL to drop the tables defined by the passed classes. * * @psalm-param list<ClassMetadata> $classes * * @return list<string> */ public function getDropSchemaSQL(array $classes) { $schema = $this->getSchemaFromMetadata($classes); $deployedSchema = $this->introspectSchema(); foreach ($schema->getTables() as $table) { if (! $deployedSchema->hasTable($table->getName())) { $schema->dropTable($table->getName()); } } if ($this->platform->supportsSequences()) { foreach ($schema->getSequences() as $sequence) { if (! $deployedSchema->hasSequence($sequence->getName())) { $schema->dropSequence($sequence->getName()); } } foreach ($schema->getTables() as $table) { $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { continue; } $columns = $primaryKey->getColumns(); if (count($columns) === 1) { $checkSequence = $table->getName() . '_' . $columns[0] . '_seq'; if ($deployedSchema->hasSequence($checkSequence) && ! $schema->hasSequence($checkSequence)) { $schema->createSequence($checkSequence); } } } } return $schema->toDropSql($this->platform); } /** * Updates the database schema of the given classes by comparing the ClassMetadata * instances to the current database schema that is inspected. * * @param mixed[] $classes * @param bool $saveMode If TRUE, only performs a partial update * without dropping assets which are scheduled for deletion. * * @return void */ public function updateSchema(array $classes, $saveMode = false) { if (func_num_args() > 1) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10153', 'Passing $saveMode to %s() is deprecated and will not be possible in Doctrine ORM 3.0.', __METHOD__ ); } $updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode); $conn = $this->em->getConnection(); foreach ($updateSchemaSql as $sql) { $conn->executeStatement($sql); } } /** * Gets the sequence of SQL statements that need to be performed in order * to bring the given class mappings in-synch with the relational schema. * * @param bool $saveMode If TRUE, only generates SQL for a partial update * that does not include SQL for dropping assets which are scheduled for deletion. * @param list<ClassMetadata> $classes The classes to consider. * * @return list<string> The sequence of SQL statements. */ public function getUpdateSchemaSql(array $classes, $saveMode = false) { if (func_num_args() > 1) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/10153', 'Passing $saveMode to %s() is deprecated and will not be possible in Doctrine ORM 3.0.', __METHOD__ ); } $toSchema = $this->getSchemaFromMetadata($classes); $fromSchema = $this->createSchemaForComparison($toSchema); if (method_exists($this->schemaManager, 'createComparator')) { $comparator = $this->schemaManager->createComparator(); } else { $comparator = new Comparator(); } $schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema); if ($saveMode) { return $schemaDiff->toSaveSql($this->platform); } if (! method_exists(AbstractPlatform::class, 'getAlterSchemaSQL')) { return $schemaDiff->toSql($this->platform); } return $this->platform->getAlterSchemaSQL($schemaDiff); } /** * Creates the schema from the database, ensuring tables from the target schema are whitelisted for comparison. */ private function createSchemaForComparison(Schema $toSchema): Schema { $connection = $this->em->getConnection(); // backup schema assets filter $config = $connection->getConfiguration(); $previousFilter = $config->getSchemaAssetsFilter(); if ($previousFilter === null) { return $this->introspectSchema(); } // whitelist assets we already know about in $toSchema, use the existing filter otherwise $config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema): bool { $assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset; return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset); }); try { return $this->introspectSchema(); } finally { // restore schema assets filter $config->setSchemaAssetsFilter($previousFilter); } } private function introspectSchema(): Schema { $method = method_exists($this->schemaManager, 'introspectSchema') ? 'introspectSchema' : 'createSchema'; return $this->schemaManager->$method(); } } orm/lib/Doctrine/ORM/Tools/SchemaValidator.php 0000644 00000036707 15120025736 0015210 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use function array_diff; use function array_key_exists; use function array_search; use function array_values; use function class_exists; use function class_parents; use function count; use function get_class; use function implode; use function in_array; /** * Performs strict validation of the mapping schema * * @link www.doctrine-project.com */ class SchemaValidator { /** @var EntityManagerInterface */ private $em; public function __construct(EntityManagerInterface $em) { $this->em = $em; } /** * Checks the internal consistency of all mapping files. * * There are several checks that can't be done at runtime or are too expensive, which can be verified * with this command. For example: * * 1. Check if a relation with "mappedBy" is actually connected to that specified field. * 2. Check if "mappedBy" and "inversedBy" are consistent to each other. * 3. Check if "referencedColumnName" attributes are really pointing to primary key columns. * * @psalm-return array<string, list<string>> */ public function validateMapping() { $errors = []; $cmf = $this->em->getMetadataFactory(); $classes = $cmf->getAllMetadata(); foreach ($classes as $class) { $ce = $this->validateClass($class); if ($ce) { $errors[$class->name] = $ce; } } return $errors; } /** * Validates a single class of the current. * * @return string[] * @psalm-return list<string> */ public function validateClass(ClassMetadataInfo $class) { if (! $class instanceof ClassMetadata) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/249', 'Passing an instance of %s to %s is deprecated, please pass a ClassMetadata instance instead.', get_class($class), __METHOD__, ClassMetadata::class ); } $ce = []; $cmf = $this->em->getMetadataFactory(); foreach ($class->fieldMappings as $fieldName => $mapping) { if (! Type::hasType($mapping['type'])) { $ce[] = "The field '" . $class->name . '#' . $fieldName . "' uses a non-existent type '" . $mapping['type'] . "'."; } } if ($class->isEmbeddedClass && count($class->associationMappings) > 0) { $ce[] = "Embeddable '" . $class->name . "' does not support associations"; return $ce; } foreach ($class->associationMappings as $fieldName => $assoc) { if (! class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) { $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; return $ce; } $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); if ($targetMetadata->isMappedSuperclass) { $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is a mapped superclass. This is not possible since there is no table that a foreign key could refer to.'; return $ce; } if ($assoc['mappedBy'] && $assoc['inversedBy']) { $ce[] = 'The association ' . $class . '#' . $fieldName . ' cannot be defined as both inverse and owning.'; } if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) { $ce[] = "Cannot map association '" . $class->name . '#' . $fieldName . ' as identifier, because ' . "the target entity '" . $targetMetadata->name . "' also maps an association as identifier."; } if ($assoc['mappedBy']) { if ($targetMetadata->hasField($assoc['mappedBy'])) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the owning side ' . 'field ' . $assoc['targetEntity'] . '#' . $assoc['mappedBy'] . ' which is not defined as association, but as field.'; } if (! $targetMetadata->hasAssociation($assoc['mappedBy'])) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the owning side ' . 'field ' . $assoc['targetEntity'] . '#' . $assoc['mappedBy'] . ' which does not exist.'; } elseif ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] === null) { $ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the inverse side of a ' . 'bi-directional relationship, but the specified mappedBy association on the target-entity ' . $assoc['targetEntity'] . '#' . $assoc['mappedBy'] . ' does not contain the required ' . "'inversedBy=\"" . $fieldName . "\"' attribute."; } elseif ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] !== $fieldName) { $ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' . $assoc['targetEntity'] . '#' . $assoc['mappedBy'] . ' are ' . 'inconsistent with each other.'; } } if ($assoc['inversedBy']) { if ($targetMetadata->hasField($assoc['inversedBy'])) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' . 'field ' . $assoc['targetEntity'] . '#' . $assoc['inversedBy'] . ' which is not defined as association.'; } if (! $targetMetadata->hasAssociation($assoc['inversedBy'])) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' . 'field ' . $assoc['targetEntity'] . '#' . $assoc['inversedBy'] . ' which does not exist.'; } elseif ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] === null) { $ce[] = 'The field ' . $class->name . '#' . $fieldName . ' is on the owning side of a ' . 'bi-directional relationship, but the specified inversedBy association on the target-entity ' . $assoc['targetEntity'] . '#' . $assoc['inversedBy'] . ' does not contain the required ' . "'mappedBy=\"" . $fieldName . "\"' attribute."; } elseif ($targetMetadata->associationMappings[$assoc['inversedBy']]['mappedBy'] !== $fieldName) { $ce[] = 'The mappings ' . $class->name . '#' . $fieldName . ' and ' . $assoc['targetEntity'] . '#' . $assoc['inversedBy'] . ' are ' . 'inconsistent with each other.'; } // Verify inverse side/owning side match each other if (array_key_exists($assoc['inversedBy'], $targetMetadata->associationMappings)) { $targetAssoc = $targetMetadata->associationMappings[$assoc['inversedBy']]; if ($assoc['type'] === ClassMetadata::ONE_TO_ONE && $targetAssoc['type'] !== ClassMetadata::ONE_TO_ONE) { $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is one-to-one, then the inversed ' . 'side ' . $targetMetadata->name . '#' . $assoc['inversedBy'] . ' has to be one-to-one as well.'; } elseif ($assoc['type'] === ClassMetadata::MANY_TO_ONE && $targetAssoc['type'] !== ClassMetadata::ONE_TO_MANY) { $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is many-to-one, then the inversed ' . 'side ' . $targetMetadata->name . '#' . $assoc['inversedBy'] . ' has to be one-to-many.'; } elseif ($assoc['type'] === ClassMetadata::MANY_TO_MANY && $targetAssoc['type'] !== ClassMetadata::MANY_TO_MANY) { $ce[] = 'If association ' . $class->name . '#' . $fieldName . ' is many-to-many, then the inversed ' . 'side ' . $targetMetadata->name . '#' . $assoc['inversedBy'] . ' has to be many-to-many as well.'; } } } if ($assoc['isOwningSide']) { if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { if (! in_array($joinColumn['referencedColumnName'], $identifierColumns, true)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '" . $class->name . "'."; break; } } $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) { if (! in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns, true)) { $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '" . $targetMetadata->name . "'."; break; } } if (count($targetMetadata->getIdentifierColumnNames()) !== count($assoc['joinTable']['inverseJoinColumns'])) { $ce[] = "The inverse join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the target entity '" . $targetMetadata->name . "', " . "however '" . implode(', ', array_diff($targetMetadata->getIdentifierColumnNames(), array_values($assoc['relationToTargetKeyColumns']))) . "' are missing."; } if (count($class->getIdentifierColumnNames()) !== count($assoc['joinTable']['joinColumns'])) { $ce[] = "The join columns of the many-to-many table '" . $assoc['joinTable']['name'] . "' " . "have to contain to ALL identifier columns of the source entity '" . $class->name . "', " . "however '" . implode(', ', array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) . "' are missing."; } } elseif ($assoc['type'] & ClassMetadata::TO_ONE) { $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] as $joinColumn) { if (! in_array($joinColumn['referencedColumnName'], $identifierColumns, true)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . "has to be a primary key column on the target entity class '" . $targetMetadata->name . "'."; } } if (count($identifierColumns) !== count($assoc['joinColumns'])) { $ids = []; foreach ($assoc['joinColumns'] as $joinColumn) { $ids[] = $joinColumn['name']; } $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . "have to match to ALL identifier columns of the target entity '" . $targetMetadata->name . "', " . "however '" . implode(', ', array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . "' are missing."; } } } if (isset($assoc['orderBy']) && $assoc['orderBy'] !== null) { foreach ($assoc['orderBy'] as $orderField => $orientation) { if (! $targetMetadata->hasField($orderField) && ! $targetMetadata->hasAssociation($orderField)) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a foreign field ' . $orderField . ' that is not a field on the target entity ' . $targetMetadata->name . '.'; continue; } if ($targetMetadata->isCollectionValuedAssociation($orderField)) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a field ' . $orderField . ' on ' . $targetMetadata->name . ' that is a collection-valued association.'; continue; } if ($targetMetadata->isAssociationInverseSide($orderField)) { $ce[] = 'The association ' . $class->name . '#' . $fieldName . ' is ordered by a field ' . $orderField . ' on ' . $targetMetadata->name . ' that is the inverse side of an association.'; continue; } } } } if ( ! $class->isInheritanceTypeNone() && ! $class->isRootEntity() && ($class->reflClass !== null && ! $class->reflClass->isAbstract()) && ! $class->isMappedSuperclass && array_search($class->name, $class->discriminatorMap, true) === false ) { $ce[] = "Entity class '" . $class->name . "' is part of inheritance hierarchy, but is " . "not mapped in the root entity '" . $class->rootEntityName . "' discriminator map. " . 'All subclasses must be listed in the discriminator map.'; } foreach ($class->subClasses as $subClass) { if (! in_array($class->name, class_parents($subClass), true)) { $ce[] = "According to the discriminator map class '" . $subClass . "' has to be a child " . "of '" . $class->name . "' but these entities are not related through inheritance."; } } return $ce; } /** * Checks if the Database Schema is in sync with the current metadata state. * * @return bool */ public function schemaInSyncWithMetadata() { return count($this->getUpdateSchemaList()) === 0; } /** * Returns the list of missing Database Schema updates. * * @return array<string> */ public function getUpdateSchemaList(): array { $schemaTool = new SchemaTool($this->em); $allMetadata = $this->em->getMetadataFactory()->getAllMetadata(); return $schemaTool->getUpdateSchemaSql($allMetadata, true); } } orm/lib/Doctrine/ORM/Tools/Setup.php 0000644 00000021641 15120025736 0013231 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\Common\Cache\ApcuCache; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\CacheProvider; use Doctrine\Common\Cache\MemcachedCache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Common\Cache\RedisCache; use Doctrine\Common\ClassLoader; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; use Doctrine\ORM\Mapping\Driver\YamlDriver; use Doctrine\ORM\ORMSetup; use Memcached; use Redis; use RuntimeException; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\MemcachedAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; use function apcu_enabled; use function class_exists; use function dirname; use function extension_loaded; use function file_exists; use function md5; use function sys_get_temp_dir; /** * Convenience class for setting up Doctrine from different installations and configurations. * * @deprecated Use {@see ORMSetup} instead. */ class Setup { /** * Use this method to register all autoloads for a downloaded Doctrine library. * Pick the directory the library was uncompressed into. * * @deprecated Use Composer's autoloader instead. * * @param string $directory * * @return void */ public static function registerAutoloadDirectory($directory) { if (! class_exists('Doctrine\Common\ClassLoader', false)) { if (file_exists($directory . '/Doctrine/Common/ClassLoader.php')) { require_once $directory . '/Doctrine/Common/ClassLoader.php'; } elseif (file_exists(dirname($directory) . '/src/ClassLoader.php')) { require_once dirname($directory) . '/src/ClassLoader.php'; } } $loader = new ClassLoader('Doctrine', $directory); $loader->register(); $loader = new ClassLoader('Symfony\Component', $directory . '/Doctrine'); $loader->register(); } /** * Creates a configuration with an annotation metadata driver. * * @param string[] $paths * @param bool $isDevMode * @param string|null $proxyDir * @param bool $useSimpleAnnotationReader * * @return Configuration */ public static function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, ?Cache $cache = null, $useSimpleAnnotationReader = true) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9443', '%s is deprecated and will be removed in Doctrine 3.0, please use %s instead.', self::class, ORMSetup::class ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths, $useSimpleAnnotationReader)); return $config; } /** * Creates a configuration with an attribute metadata driver. * * @param string[] $paths * @param bool $isDevMode * @param string|null $proxyDir */ public static function createAttributeMetadataConfiguration( array $paths, $isDevMode = false, $proxyDir = null, ?Cache $cache = null ): Configuration { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9443', '%s is deprecated and will be removed in Doctrine 3.0, please use %s instead.', self::class, ORMSetup::class ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new AttributeDriver($paths)); return $config; } /** * Creates a configuration with an XML metadata driver. * * @param string[] $paths * @param bool $isDevMode * @param string|null $proxyDir * * @return Configuration */ public static function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, ?Cache $cache = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9443', '%s is deprecated and will be removed in Doctrine 3.0, please use %s instead.', self::class, ORMSetup::class ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new XmlDriver($paths)); return $config; } /** * Creates a configuration with a YAML metadata driver. * * @deprecated YAML metadata mapping is deprecated and will be removed in 3.0 * * @param string[] $paths * @param bool $isDevMode * @param string|null $proxyDir * * @return Configuration */ public static function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, ?Cache $cache = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8465', 'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.' ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new YamlDriver($paths)); return $config; } /** * Creates a configuration without a metadata driver. * * @param bool $isDevMode * @param string|null $proxyDir * * @return Configuration */ public static function createConfiguration($isDevMode = false, $proxyDir = null, ?Cache $cache = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9443', '%s is deprecated and will be removed in Doctrine 3.0, please use %s instead.', self::class, ORMSetup::class ); $proxyDir = $proxyDir ?: sys_get_temp_dir(); $cache = self::createCacheConfiguration($isDevMode, $proxyDir, $cache); $config = new Configuration(); $config->setMetadataCache(CacheAdapter::wrap($cache)); $config->setQueryCache(CacheAdapter::wrap($cache)); $config->setResultCache(CacheAdapter::wrap($cache)); $config->setProxyDir($proxyDir); $config->setProxyNamespace('DoctrineProxies'); $config->setAutoGenerateProxyClasses($isDevMode); return $config; } private static function createCacheConfiguration(bool $isDevMode, string $proxyDir, ?Cache $cache): Cache { $cache = self::createCacheInstance($isDevMode, $cache); if (! $cache instanceof CacheProvider) { return $cache; } $namespace = $cache->getNamespace(); if ($namespace !== '') { $namespace .= ':'; } $cache->setNamespace($namespace . 'dc2_' . md5($proxyDir) . '_'); // to avoid collisions return $cache; } private static function createCacheInstance(bool $isDevMode, ?Cache $cache): Cache { if ($cache !== null) { return $cache; } if (! class_exists(ArrayCache::class) && ! class_exists(ArrayAdapter::class)) { throw new RuntimeException('Setup tool cannot configure caches without doctrine/cache 1.11 or symfony/cache. Please add an explicit dependency to either library.'); } if ($isDevMode === true) { $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter(); } elseif (extension_loaded('apcu') && apcu_enabled()) { $cache = class_exists(ApcuCache::class) ? new ApcuCache() : new ApcuAdapter(); } elseif (extension_loaded('memcached') && (class_exists(MemcachedCache::class) || MemcachedAdapter::isSupported())) { $memcached = new Memcached(); $memcached->addServer('127.0.0.1', 11211); if (class_exists(MemcachedCache::class)) { $cache = new MemcachedCache(); $cache->setMemcached($memcached); } else { $cache = new MemcachedAdapter($memcached); } } elseif (extension_loaded('redis')) { $redis = new Redis(); $redis->connect('127.0.0.1'); if (class_exists(RedisCache::class)) { $cache = new RedisCache(); $cache->setRedis($redis); } else { $cache = new RedisAdapter($redis); } } else { $cache = class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter(); } return $cache instanceof Cache ? $cache : DoctrineProvider::wrap($cache); } } orm/lib/Doctrine/ORM/Tools/ToolEvents.php 0000644 00000001461 15120025736 0014231 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; class ToolEvents { /** * The postGenerateSchemaTable event occurs in SchemaTool#getSchemaFromMetadata() * whenever an entity class is transformed into its table representation. It receives * the current non-complete Schema instance, the Entity Metadata Class instance and * the Schema Table instance of this entity. */ public const postGenerateSchemaTable = 'postGenerateSchemaTable'; /** * The postGenerateSchema event is triggered in SchemaTool#getSchemaFromMetadata() * after all entity classes have been transformed into the related Schema structure. * The EventArgs contain the EntityManager and the created Schema instance. */ public const postGenerateSchema = 'postGenerateSchema'; } orm/lib/Doctrine/ORM/Tools/ToolsException.php 0000644 00000001314 15120025736 0015103 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Tools; use Doctrine\ORM\Exception\ORMException; use Throwable; use function sprintf; /** * Tools related Exceptions. */ class ToolsException extends ORMException { public static function schemaToolFailure(string $sql, Throwable $e): self { return new self( "Schema-Tool failed with Error '" . $e->getMessage() . "' while executing DDL: " . $sql, 0, $e ); } /** * @param string $type * * @return ToolsException */ public static function couldNotMapDoctrine1Type($type) { return new self(sprintf("Could not map doctrine 1 type '%s'!", $type)); } } orm/lib/Doctrine/ORM/Utility/HierarchyDiscriminatorResolver.php 0000644 00000002453 15120025736 0020664 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Utility; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Mapping\ClassMetadata; /** @internal This class exists only to avoid code duplication, do not reuse it externally */ final class HierarchyDiscriminatorResolver { private function __construct() { } /** * This method is needed to make INSTANCEOF work correctly with inheritance: if the class at hand has inheritance, * it extracts all the discriminators from the child classes and returns them * * @return null[] * @psalm-return array<array-key, null> */ public static function resolveDiscriminatorsForClass( ClassMetadata $rootClassMetadata, EntityManagerInterface $entityManager ): array { $hierarchyClasses = $rootClassMetadata->subClasses; $hierarchyClasses[] = $rootClassMetadata->name; $discriminators = []; foreach ($hierarchyClasses as $class) { $currentMetadata = $entityManager->getClassMetadata($class); $currentDiscriminator = $currentMetadata->discriminatorValue; if ($currentDiscriminator !== null) { $discriminators[$currentDiscriminator] = null; } } return $discriminators; } } orm/lib/Doctrine/ORM/Utility/IdentifierFlattener.php 0000644 00000005613 15120025736 0016424 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Utility; use BackedEnum; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\UnitOfWork; use Doctrine\Persistence\Mapping\ClassMetadataFactory; use function assert; use function implode; use function is_a; /** * The IdentifierFlattener utility now houses some of the identifier manipulation logic from unit of work, so that it * can be re-used elsewhere. */ final class IdentifierFlattener { /** * The UnitOfWork used to coordinate object-level transactions. * * @var UnitOfWork */ private $unitOfWork; /** * The metadata factory, used to retrieve the ORM metadata of entity classes. * * @var ClassMetadataFactory */ private $metadataFactory; /** * Initializes a new IdentifierFlattener instance, bound to the given EntityManager. */ public function __construct(UnitOfWork $unitOfWork, ClassMetadataFactory $metadataFactory) { $this->unitOfWork = $unitOfWork; $this->metadataFactory = $metadataFactory; } /** * convert foreign identifiers into scalar foreign key values to avoid object to string conversion failures. * * @param mixed[] $id * * @return mixed[] * @psalm-return array<string, mixed> */ public function flattenIdentifier(ClassMetadata $class, array $id): array { $flatId = []; foreach ($class->identifier as $field) { if (isset($class->associationMappings[$field]) && isset($id[$field]) && is_a($id[$field], $class->associationMappings[$field]['targetEntity'])) { $targetClassMetadata = $this->metadataFactory->getMetadataFor( $class->associationMappings[$field]['targetEntity'] ); assert($targetClassMetadata instanceof ClassMetadata); if ($this->unitOfWork->isInIdentityMap($id[$field])) { $associatedId = $this->flattenIdentifier($targetClassMetadata, $this->unitOfWork->getEntityIdentifier($id[$field])); } else { $associatedId = $this->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($id[$field])); } $flatId[$field] = implode(' ', $associatedId); } elseif (isset($class->associationMappings[$field])) { $associatedId = []; foreach ($class->associationMappings[$field]['joinColumns'] as $joinColumn) { $associatedId[] = $id[$joinColumn['name']]; } $flatId[$field] = implode(' ', $associatedId); } else { if ($id[$field] instanceof BackedEnum) { $flatId[$field] = $id[$field]->value; } else { $flatId[$field] = $id[$field]; } } } return $flatId; } } orm/lib/Doctrine/ORM/Utility/PersisterHelper.php 0000644 00000006712 15120025736 0015616 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM\Utility; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\QueryException; use RuntimeException; use function sprintf; /** * The PersisterHelper contains logic to infer binding types which is used in * several persisters. * * @link www.doctrine-project.org */ class PersisterHelper { /** * @param string $fieldName * * @return array<int, string> * * @throws QueryException */ public static function getTypeOfField($fieldName, ClassMetadata $class, EntityManagerInterface $em) { if (isset($class->fieldMappings[$fieldName])) { return [$class->fieldMappings[$fieldName]['type']]; } if (! isset($class->associationMappings[$fieldName])) { return []; } $assoc = $class->associationMappings[$fieldName]; if (! $assoc['isOwningSide']) { return self::getTypeOfField($assoc['mappedBy'], $em->getClassMetadata($assoc['targetEntity']), $em); } if ($assoc['type'] & ClassMetadata::MANY_TO_MANY) { $joinData = $assoc['joinTable']; } else { $joinData = $assoc; } $types = []; $targetClass = $em->getClassMetadata($assoc['targetEntity']); foreach ($joinData['joinColumns'] as $joinColumn) { $types[] = self::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $em); } return $types; } /** * @param string $columnName * * @return string * * @throws RuntimeException */ public static function getTypeOfColumn($columnName, ClassMetadata $class, EntityManagerInterface $em) { if (isset($class->fieldNames[$columnName])) { $fieldName = $class->fieldNames[$columnName]; if (isset($class->fieldMappings[$fieldName])) { return $class->fieldMappings[$fieldName]['type']; } } // iterate over to-one association mappings foreach ($class->associationMappings as $assoc) { if (! isset($assoc['joinColumns'])) { continue; } foreach ($assoc['joinColumns'] as $joinColumn) { if ($joinColumn['name'] === $columnName) { $targetColumnName = $joinColumn['referencedColumnName']; $targetClass = $em->getClassMetadata($assoc['targetEntity']); return self::getTypeOfColumn($targetColumnName, $targetClass, $em); } } } // iterate over to-many association mappings foreach ($class->associationMappings as $assoc) { if (! (isset($assoc['joinTable']) && isset($assoc['joinTable']['joinColumns']))) { continue; } foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { if ($joinColumn['name'] === $columnName) { $targetColumnName = $joinColumn['referencedColumnName']; $targetClass = $em->getClassMetadata($assoc['targetEntity']); return self::getTypeOfColumn($targetColumnName, $targetClass, $em); } } } throw new RuntimeException(sprintf( 'Could not resolve type of column "%s" of class "%s"', $columnName, $class->getName() )); } } orm/lib/Doctrine/ORM/AbstractQuery.php 0000644 00000121066 15120025736 0013624 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BackedEnum; use Countable; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Result; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver; use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\ORM\Cache\TimestampCacheKey; use Doctrine\ORM\Internal\Hydration\IterableResult; use Doctrine\ORM\Mapping\MappingException as ORMMappingException; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\Mapping\MappingException; use LogicException; use Psr\Cache\CacheItemPoolInterface; use Traversable; use function array_map; use function array_shift; use function assert; use function count; use function func_num_args; use function in_array; use function is_array; use function is_numeric; use function is_object; use function is_scalar; use function is_string; use function iterator_count; use function iterator_to_array; use function ksort; use function method_exists; use function reset; use function serialize; use function sha1; /** * Base contract for ORM queries. Base class for Query and NativeQuery. * * @link www.doctrine-project.org */ abstract class AbstractQuery { /* Hydration mode constants */ /** * Hydrates an object graph. This is the default behavior. */ public const HYDRATE_OBJECT = 1; /** * Hydrates an array graph. */ public const HYDRATE_ARRAY = 2; /** * Hydrates a flat, rectangular result set with scalar values. */ public const HYDRATE_SCALAR = 3; /** * Hydrates a single scalar value. */ public const HYDRATE_SINGLE_SCALAR = 4; /** * Very simple object hydrator (optimized for performance). */ public const HYDRATE_SIMPLEOBJECT = 5; /** * Hydrates scalar column value. */ public const HYDRATE_SCALAR_COLUMN = 6; /** * The parameter map of this query. * * @var ArrayCollection|Parameter[] * @psalm-var ArrayCollection<int, Parameter> */ protected $parameters; /** * The user-specified ResultSetMapping to use. * * @var ResultSetMapping|null */ protected $_resultSetMapping; /** * The entity manager used by this query object. * * @var EntityManagerInterface */ protected $_em; /** * The map of query hints. * * @psalm-var array<string, mixed> */ protected $_hints = []; /** * The hydration mode. * * @var string|int * @psalm-var string|AbstractQuery::HYDRATE_* */ protected $_hydrationMode = self::HYDRATE_OBJECT; /** @var QueryCacheProfile|null */ protected $_queryCacheProfile; /** * Whether or not expire the result cache. * * @var bool */ protected $_expireResultCache = false; /** @var QueryCacheProfile|null */ protected $_hydrationCacheProfile; /** * Whether to use second level cache, if available. * * @var bool */ protected $cacheable = false; /** @var bool */ protected $hasCache = false; /** * Second level cache region name. * * @var string|null */ protected $cacheRegion; /** * Second level query cache mode. * * @var int|null * @psalm-var Cache::MODE_*|null */ protected $cacheMode; /** @var CacheLogger|null */ protected $cacheLogger; /** @var int */ protected $lifetime = 0; /** * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>. */ public function __construct(EntityManagerInterface $em) { $this->_em = $em; $this->parameters = new ArrayCollection(); $this->_hints = $em->getConfiguration()->getDefaultQueryHints(); $this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { $this->cacheLogger = $em->getConfiguration() ->getSecondLevelCacheConfiguration() ->getCacheLogger(); } } /** * Enable/disable second level query (result) caching for this query. * * @param bool $cacheable * * @return $this */ public function setCacheable($cacheable) { $this->cacheable = (bool) $cacheable; return $this; } /** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */ public function isCacheable() { return $this->cacheable; } /** * @param string $cacheRegion * * @return $this */ public function setCacheRegion($cacheRegion) { $this->cacheRegion = (string) $cacheRegion; return $this; } /** * Obtain the name of the second level query cache region in which query results will be stored * * @return string|null The cache region name; NULL indicates the default region. */ public function getCacheRegion() { return $this->cacheRegion; } /** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */ protected function isCacheEnabled() { return $this->cacheable && $this->hasCache; } /** @return int */ public function getLifetime() { return $this->lifetime; } /** * Sets the life-time for this query into second level cache. * * @param int $lifetime * * @return $this */ public function setLifetime($lifetime) { $this->lifetime = (int) $lifetime; return $this; } /** * @return int|null * @psalm-return Cache::MODE_*|null */ public function getCacheMode() { return $this->cacheMode; } /** * @param int $cacheMode * @psalm-param Cache::MODE_* $cacheMode * * @return $this */ public function setCacheMode($cacheMode) { $this->cacheMode = (int) $cacheMode; return $this; } /** * Gets the SQL query that corresponds to this query object. * The returned SQL syntax depends on the connection driver that is used * by this query object at the time of this method call. * * @return list<string>|string SQL query */ abstract public function getSQL(); /** * Retrieves the associated EntityManager of this Query instance. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->_em; } /** * Frees the resources used by the query object. * * Resets Parameters, Parameter Types and Query Hints. * * @return void */ public function free() { $this->parameters = new ArrayCollection(); $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints(); } /** * Get all defined parameters. * * @return ArrayCollection The defined query parameters. * @psalm-return ArrayCollection<int, Parameter> */ public function getParameters() { return $this->parameters; } /** * Gets a query parameter. * * @param int|string $key The key (index or name) of the bound parameter. * * @return Parameter|null The value of the bound parameter, or NULL if not available. */ public function getParameter($key) { $key = Query\Parameter::normalizeName($key); $filteredParameters = $this->parameters->filter( static function (Query\Parameter $parameter) use ($key): bool { $parameterName = $parameter->getName(); return $key === $parameterName; } ); return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null; } /** * Sets a collection of query parameters. * * @param ArrayCollection|mixed[] $parameters * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters * * @return $this */ public function setParameters($parameters) { if (is_array($parameters)) { /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */ $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameterCollection->add(new Parameter($key, $value)); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Sets a query parameter. * * @param string|int $key The parameter position or name. * @param mixed $value The parameter value. * @param string|int|null $type The parameter type. If specified, the given value will be run through * the type conversion of this type. This is usually not needed for * strings and numeric types. * * @return $this */ public function setParameter($key, $value, $type = null) { $existingParameter = $this->getParameter($key); if ($existingParameter !== null) { $existingParameter->setValue($value, $type); return $this; } $this->parameters->add(new Parameter($key, $value, $type)); return $this; } /** * Processes an individual parameter value. * * @param mixed $value * * @return mixed * * @throws ORMInvalidArgumentException */ public function processParameterValue($value) { if (is_scalar($value)) { return $value; } if ($value instanceof Collection) { $value = iterator_to_array($value); } if (is_array($value)) { $value = $this->processArrayParameterValue($value); return $value; } if ($value instanceof Mapping\ClassMetadata) { return $value->name; } if ($value instanceof BackedEnum) { return $value->value; } if (! is_object($value)) { return $value; } try { $class = ClassUtils::getClass($value); $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value); if ($value === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class); } } catch (MappingException | ORMMappingException $e) { /* Silence any mapping exceptions. These can occur if the object in question is not a mapped entity, in which case we just don't do any preparation on the value. Depending on MappingDriver, either MappingException or ORMMappingException is thrown. */ $value = $this->potentiallyProcessIterable($value); } return $value; } /** * If no mapping is detected, trying to resolve the value as a Traversable * * @param mixed $value * * @return mixed */ private function potentiallyProcessIterable($value) { if ($value instanceof Traversable) { $value = iterator_to_array($value); $value = $this->processArrayParameterValue($value); } return $value; } /** * Process a parameter value which was previously identified as an array * * @param mixed[] $value * * @return mixed[] */ private function processArrayParameterValue(array $value): array { foreach ($value as $key => $paramValue) { $paramValue = $this->processParameterValue($paramValue); $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue; } return $value; } /** * Sets the ResultSetMapping that should be used for hydration. * * @return $this */ public function setResultSetMapping(Query\ResultSetMapping $rsm) { $this->translateNamespaces($rsm); $this->_resultSetMapping = $rsm; return $this; } /** * Gets the ResultSetMapping used for hydration. * * @return ResultSetMapping|null */ protected function getResultSetMapping() { return $this->_resultSetMapping; } /** * Allows to translate entity namespaces to full qualified names. */ private function translateNamespaces(Query\ResultSetMapping $rsm): void { $translate = function ($alias): string { return $this->_em->getClassMetadata($alias)->getName(); }; $rsm->aliasMap = array_map($translate, $rsm->aliasMap); $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses); } /** * Set a cache profile for hydration caching. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * Important: Hydration caching does NOT register entities in the * UnitOfWork when retrieved from the cache. Never use result cached * entities for requests that also flush the EntityManager. If you want * some form of caching with UnitOfWork registration you should use * {@see AbstractQuery::setResultCacheProfile()}. * * @return $this * * @example * $lifetime = 100; * $resultKey = "abc"; * $query->setHydrationCacheProfile(new QueryCacheProfile()); * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey)); */ public function setHydrationCacheProfile(?QueryCacheProfile $profile = null) { if ($profile === null) { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9791', 'Calling %s without arguments is deprecated, pass null instead.', __METHOD__ ); } $this->_hydrationCacheProfile = null; return $this; } // DBAL 2 if (! method_exists(QueryCacheProfile::class, 'setResultCache')) { if (! $profile->getResultCacheDriver()) { $defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache(); if ($defaultHydrationCacheImpl) { $profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultHydrationCacheImpl)); } } } elseif (! $profile->getResultCache()) { $defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache(); if ($defaultHydrationCacheImpl) { $profile = $profile->setResultCache($defaultHydrationCacheImpl); } } $this->_hydrationCacheProfile = $profile; return $this; } /** @return QueryCacheProfile|null */ public function getHydrationCacheProfile() { return $this->_hydrationCacheProfile; } /** * Set a cache profile for the result cache. * * If no result cache driver is set in the QueryCacheProfile, the default * result cache driver is used from the configuration. * * @return $this */ public function setResultCacheProfile(?QueryCacheProfile $profile = null) { if ($profile === null) { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9791', 'Calling %s without arguments is deprecated, pass null instead.', __METHOD__ ); } $this->_queryCacheProfile = null; return $this; } // DBAL 2 if (! method_exists(QueryCacheProfile::class, 'setResultCache')) { if (! $profile->getResultCacheDriver()) { $defaultResultCacheDriver = $this->_em->getConfiguration()->getResultCache(); if ($defaultResultCacheDriver) { $profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultResultCacheDriver)); } } } elseif (! $profile->getResultCache()) { $defaultResultCache = $this->_em->getConfiguration()->getResultCache(); if ($defaultResultCache) { $profile = $profile->setResultCache($defaultResultCache); } } $this->_queryCacheProfile = $profile; return $this; } /** * Defines a cache driver to be used for caching result sets and implicitly enables caching. * * @deprecated Use {@see setResultCache()} instead. * * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver * * @return $this * * @throws InvalidResultCacheDriver */ public function setResultCacheDriver($resultCacheDriver = null) { /** @phpstan-ignore-next-line */ if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw InvalidResultCacheDriver::create(); } return $this->setResultCache($resultCacheDriver ? CacheAdapter::wrap($resultCacheDriver) : null); } /** * Defines a cache driver to be used for caching result sets and implicitly enables caching. * * @return $this */ public function setResultCache(?CacheItemPoolInterface $resultCache = null) { if ($resultCache === null) { if (func_num_args() < 1) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9791', 'Calling %s without arguments is deprecated, pass null instead.', __METHOD__ ); } if ($this->_queryCacheProfile) { $this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey()); } return $this; } // DBAL 2 if (! method_exists(QueryCacheProfile::class, 'setResultCache')) { $resultCacheDriver = DoctrineProvider::wrap($resultCache); $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver) : new QueryCacheProfile(0, null, $resultCacheDriver); return $this; } $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setResultCache($resultCache) : new QueryCacheProfile(0, null, $resultCache); return $this; } /** * Returns the cache driver used for caching result sets. * * @deprecated * * @return \Doctrine\Common\Cache\Cache Cache driver */ public function getResultCacheDriver() { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { return $this->_queryCacheProfile->getResultCacheDriver(); } return $this->_em->getConfiguration()->getResultCacheImpl(); } /** * Set whether or not to cache the results of this query and if so, for * how long and which ID to use for the cache entry. * * @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead. * * @param bool $useCache Whether or not to cache the results of this query. * @param int $lifetime How long the cache entry is valid, in seconds. * @param string $resultCacheId ID to use for the cache entry. * * @return $this */ public function useResultCache($useCache, $lifetime = null, $resultCacheId = null) { return $useCache ? $this->enableResultCache($lifetime, $resultCacheId) : $this->disableResultCache(); } /** * Enables caching of the results of this query, for given or default amount of seconds * and optionally specifies which ID to use for the cache entry. * * @param int|null $lifetime How long the cache entry is valid, in seconds. * @param string|null $resultCacheId ID to use for the cache entry. * * @return $this */ public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self { $this->setResultCacheLifetime($lifetime); $this->setResultCacheId($resultCacheId); return $this; } /** * Disables caching of the results of this query. * * @return $this */ public function disableResultCache(): self { $this->_queryCacheProfile = null; return $this; } /** * Defines how long the result cache will be active before expire. * * @param int|null $lifetime How long the cache entry is valid, in seconds. * * @return $this */ public function setResultCacheLifetime($lifetime) { $lifetime = (int) $lifetime; if ($this->_queryCacheProfile) { $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime); return $this; } $this->_queryCacheProfile = new QueryCacheProfile($lifetime); $cache = $this->_em->getConfiguration()->getResultCache(); if (! $cache) { return $this; } // Compatibility for DBAL 2 if (! method_exists($this->_queryCacheProfile, 'setResultCache')) { $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver(DoctrineProvider::wrap($cache)); return $this; } $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCache($cache); return $this; } /** * Retrieves the lifetime of resultset cache. * * @deprecated * * @return int */ public function getResultCacheLifetime() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0; } /** * Defines if the result cache is active or not. * * @param bool $expire Whether or not to force resultset cache expiration. * * @return $this */ public function expireResultCache($expire = true) { $this->_expireResultCache = $expire; return $this; } /** * Retrieves if the resultset cache is active or not. * * @return bool */ public function getExpireResultCache() { return $this->_expireResultCache; } /** @return QueryCacheProfile|null */ public function getQueryCacheProfile() { return $this->_queryCacheProfile; } /** * Change the default fetch mode of an association for this query. * * @param class-string $class * @param string $assocName * @param int $fetchMode * @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode * * @return $this */ public function setFetchMode($class, $assocName, $fetchMode) { if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGER, Mapping\ClassMetadata::FETCH_LAZY], true)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9777', 'Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.', __METHOD__ ); $fetchMode = Mapping\ClassMetadata::FETCH_LAZY; } $this->_hints['fetchMode'][$class][$assocName] = $fetchMode; return $this; } /** * Defines the processing mode to be used during hydration / result set transformation. * * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process. * One of the Query::HYDRATE_* constants. * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode * * @return $this */ public function setHydrationMode($hydrationMode) { $this->_hydrationMode = $hydrationMode; return $this; } /** * Gets the hydration mode currently used by the query. * * @return string|int * @psalm-return string|AbstractQuery::HYDRATE_* */ public function getHydrationMode() { return $this->_hydrationMode; } /** * Gets the list of results for the query. * * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). * * @param string|int $hydrationMode * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode * * @return mixed */ public function getResult($hydrationMode = self::HYDRATE_OBJECT) { return $this->execute(null, $hydrationMode); } /** * Gets the array of results for the query. * * Alias for execute(null, HYDRATE_ARRAY). * * @return mixed[] */ public function getArrayResult() { return $this->execute(null, self::HYDRATE_ARRAY); } /** * Gets one-dimensional array of results for the query. * * Alias for execute(null, HYDRATE_SCALAR_COLUMN). * * @return mixed[] */ public function getSingleColumnResult() { return $this->execute(null, self::HYDRATE_SCALAR_COLUMN); } /** * Gets the scalar results for the query. * * Alias for execute(null, HYDRATE_SCALAR). * * @return mixed[] */ public function getScalarResult() { return $this->execute(null, self::HYDRATE_SCALAR); } /** * Get exactly one result or null. * * @param string|int|null $hydrationMode * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return mixed * * @throws NonUniqueResultException */ public function getOneOrNullResult($hydrationMode = null) { try { $result = $this->execute(null, $hydrationMode); } catch (NoResultException $e) { return null; } if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { return null; } if (! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException(); } return array_shift($result); } /** * Gets the single result of the query. * * Enforces the presence as well as the uniqueness of the result. * * If the result is not unique, a NonUniqueResultException is thrown. * If there is no result, a NoResultException is thrown. * * @param string|int|null $hydrationMode * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return mixed * * @throws NonUniqueResultException If the query result is not unique. * @throws NoResultException If the query returned no result. */ public function getSingleResult($hydrationMode = null) { $result = $this->execute(null, $hydrationMode); if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { throw new NoResultException(); } if (! is_array($result)) { return $result; } if (count($result) > 1) { throw new NonUniqueResultException(); } return array_shift($result); } /** * Gets the single scalar result of the query. * * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). * * @return mixed The scalar result. * * @throws NoResultException If the query returned no result. * @throws NonUniqueResultException If the query result is not unique. */ public function getSingleScalarResult() { return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR); } /** * Sets a query hint. If the hint name is not recognized, it is silently ignored. * * @param string $name The name of the hint. * @param mixed $value The value of the hint. * * @return $this */ public function setHint($name, $value) { $this->_hints[$name] = $value; return $this; } /** * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. * * @param string $name The name of the hint. * * @return mixed The value of the hint or FALSE, if the hint name is not recognized. */ public function getHint($name) { return $this->_hints[$name] ?? false; } /** * Check if the query has a hint * * @param string $name The name of the hint * * @return bool False if the query does not have any hint */ public function hasHint($name) { return isset($this->_hints[$name]); } /** * Return the key value map of query hints that are currently set. * * @return array<string,mixed> */ public function getHints() { return $this->_hints; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterate over the result. * * @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463 * * @param ArrayCollection|mixed[]|null $parameters The query parameters. * @param string|int|null $hydrationMode The hydration mode to use. * @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use. * * @return IterableResult */ public function iterate($parameters = null, $hydrationMode = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8463', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.', __METHOD__ ); if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if (! empty($parameters)) { $this->setParameters($parameters); } $rsm = $this->getResultSetMapping(); if ($rsm === null) { throw new LogicException('Uninitialized result set mapping.'); } $stmt = $this->_doExecute(); return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints); } /** * Executes the query and returns an iterable that can be used to incrementally * iterate over the result. * * @param ArrayCollection|array|mixed[] $parameters The query parameters. * @param string|int|null $hydrationMode The hydration mode to use. * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return iterable<mixed> */ public function toIterable(iterable $parameters = [], $hydrationMode = null): iterable { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if ( ($this->isCountable($parameters) && count($parameters) !== 0) || ($parameters instanceof Traversable && iterator_count($parameters) !== 0) ) { $this->setParameters($parameters); } $rsm = $this->getResultSetMapping(); if ($rsm === null) { throw new LogicException('Uninitialized result set mapping.'); } if ($rsm->isMixed && count($rsm->scalarMappings) > 0) { throw QueryException::iterateWithMixedResultNotAllowed(); } $stmt = $this->_doExecute(); return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints); } /** * Executes the query. * * @param ArrayCollection|mixed[]|null $parameters Query parameters. * @param string|int|null $hydrationMode Processing mode to be used during the hydration process. * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return mixed */ public function execute($parameters = null, $hydrationMode = null) { if ($this->cacheable && $this->isCacheEnabled()) { return $this->executeUsingQueryCache($parameters, $hydrationMode); } return $this->executeIgnoreQueryCache($parameters, $hydrationMode); } /** * Execute query ignoring second level cache. * * @param ArrayCollection|mixed[]|null $parameters * @param string|int|null $hydrationMode * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return mixed */ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); } if (! empty($parameters)) { $this->setParameters($parameters); } $setCacheEntry = static function ($data): void { }; if ($this->_hydrationCacheProfile !== null) { [$cacheKey, $realCacheKey] = $this->getHydrationCacheId(); $cache = $this->getHydrationCache(); $cacheItem = $cache->getItem($cacheKey); $result = $cacheItem->isHit() ? $cacheItem->get() : []; if (isset($result[$realCacheKey])) { return $result[$realCacheKey]; } if (! $result) { $result = []; } $setCacheEntry = static function ($data) use ($cache, $result, $cacheItem, $realCacheKey): void { $cache->save($cacheItem->set($result + [$realCacheKey => $data])); }; } $stmt = $this->_doExecute(); if (is_numeric($stmt)) { $setCacheEntry($stmt); return $stmt; } $rsm = $this->getResultSetMapping(); if ($rsm === null) { throw new LogicException('Uninitialized result set mapping.'); } $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints); $setCacheEntry($data); return $data; } private function getHydrationCache(): CacheItemPoolInterface { assert($this->_hydrationCacheProfile !== null); // Support for DBAL 2 if (! method_exists($this->_hydrationCacheProfile, 'getResultCache')) { $cacheDriver = $this->_hydrationCacheProfile->getResultCacheDriver(); assert($cacheDriver !== null); return CacheAdapter::wrap($cacheDriver); } $cache = $this->_hydrationCacheProfile->getResultCache(); assert($cache !== null); return $cache; } /** * Load from second level cache or executes the query and put into cache. * * @param ArrayCollection|mixed[]|null $parameters * @param string|int|null $hydrationMode * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode * * @return mixed */ private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { $rsm = $this->getResultSetMapping(); if ($rsm === null) { throw new LogicException('Uninitialized result set mapping.'); } $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); $queryKey = new QueryCacheKey( $this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL, $this->getTimestampKey() ); $result = $queryCache->get($queryKey, $rsm, $this->_hints); if ($result !== null) { if ($this->cacheLogger) { $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey); } return $result; } $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints); if ($this->cacheLogger) { $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey); if ($cached) { $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey); } } return $result; } private function getTimestampKey(): ?TimestampCacheKey { assert($this->_resultSetMapping !== null); $entityName = reset($this->_resultSetMapping->aliasMap); if (empty($entityName)) { return null; } $metadata = $this->_em->getClassMetadata($entityName); return new Cache\TimestampCacheKey($metadata->rootEntityName); } /** * Get the result cache id to use to store the result set cache entry. * Will return the configured id if it exists otherwise a hash will be * automatically generated for you. * * @return string[] ($key, $hash) * @psalm-return array{string, string} ($key, $hash) */ protected function getHydrationCacheId() { $parameters = []; $types = []; foreach ($this->getParameters() as $parameter) { $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); $types[$parameter->getName()] = $parameter->getType(); } $sql = $this->getSQL(); assert(is_string($sql)); $queryCacheProfile = $this->getHydrationCacheProfile(); $hints = $this->getHints(); $hints['hydrationMode'] = $this->getHydrationMode(); ksort($hints); assert($queryCacheProfile !== null); return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints); } /** * Set the result cache id to use to store the result set cache entry. * If this is not explicitly set by the developer then a hash is automatically * generated for you. * * @param string|null $id * * @return $this */ public function setResultCacheId($id) { if (! $this->_queryCacheProfile) { return $this->setResultCacheProfile(new QueryCacheProfile(0, $id)); } $this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id); return $this; } /** * Get the result cache id to use to store the result set cache entry if set. * * @deprecated * * @return string|null */ public function getResultCacheId() { return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null; } /** * Executes the query and returns a the resulting Statement object. * * @return Result|int The executed database statement that holds * the results, or an integer indicating how * many rows were affected. */ abstract protected function _doExecute(); /** * Cleanup Query resource when clone is called. * * @return void */ public function __clone() { $this->parameters = new ArrayCollection(); $this->_hints = []; $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints(); } /** * Generates a string of currently query to use for the cache second level cache. * * @return string */ protected function getHash() { $query = $this->getSQL(); assert(is_string($query)); $hints = $this->getHints(); $params = array_map(function (Parameter $parameter) { $value = $parameter->getValue(); // Small optimization // Does not invoke processParameterValue for scalar value if (is_scalar($value)) { return $value; } return $this->processParameterValue($value); }, $this->parameters->getValues()); ksort($hints); return sha1($query . '-' . serialize($params) . '-' . serialize($hints)); } /** @param iterable<mixed> $subject */ private function isCountable(iterable $subject): bool { return $subject instanceof Countable || is_array($subject); } } orm/lib/Doctrine/ORM/Cache.php 0000644 00000011333 15120025736 0012031 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\ORM\Cache\QueryCache; use Doctrine\ORM\Cache\Region; /** * Provides an API for querying/managing the second level cache regions. */ interface Cache { public const DEFAULT_QUERY_REGION_NAME = 'query_cache_region'; public const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region'; /** * May read items from the cache, but will not add items. */ public const MODE_GET = 1; /** * Will never read items from the cache, * but will add items to the cache as it reads them from the database. */ public const MODE_PUT = 2; /** * May read items from the cache, and add items to the cache. */ public const MODE_NORMAL = 3; /** * The query will never read items from the cache, * but will refresh items to the cache as it reads them from the database. */ public const MODE_REFRESH = 4; /** * @param string $className The entity class. * * @return Region|null */ public function getEntityCacheRegion($className); /** * @param string $className The entity class. * @param string $association The field name that represents the association. * * @return Region|null */ public function getCollectionCacheRegion($className, $association); /** * Determine whether the cache contains data for the given entity "instance". * * @param string $className The entity class. * @param mixed $identifier The entity identifier * * @return bool true if the underlying cache contains corresponding data; false otherwise. */ public function containsEntity($className, $identifier); /** * Evicts the entity data for a particular entity "instance". * * @param string $className The entity class. * @param mixed $identifier The entity identifier. * * @return void */ public function evictEntity($className, $identifier); /** * Evicts all entity data from the given region. * * @param string $className The entity metadata. * * @return void */ public function evictEntityRegion($className); /** * Evict data from all entity regions. * * @return void */ public function evictEntityRegions(); /** * Determine whether the cache contains data for the given collection. * * @param string $className The entity class. * @param string $association The field name that represents the association. * @param mixed $ownerIdentifier The identifier of the owning entity. * * @return bool true if the underlying cache contains corresponding data; false otherwise. */ public function containsCollection($className, $association, $ownerIdentifier); /** * Evicts the cache data for the given identified collection instance. * * @param string $className The entity class. * @param string $association The field name that represents the association. * @param mixed $ownerIdentifier The identifier of the owning entity. * * @return void */ public function evictCollection($className, $association, $ownerIdentifier); /** * Evicts all entity data from the given region. * * @param string $className The entity class. * @param string $association The field name that represents the association. * * @return void */ public function evictCollectionRegion($className, $association); /** * Evict data from all collection regions. * * @return void */ public function evictCollectionRegions(); /** * Determine whether the cache contains data for the given query. * * @param string $regionName The cache name given to the query. * * @return bool true if the underlying cache contains corresponding data; false otherwise. */ public function containsQuery($regionName); /** * Evicts all cached query results under the given name, or default query cache if the region name is NULL. * * @param string|null $regionName The cache name associated to the queries being cached. * * @return void */ public function evictQueryRegion($regionName = null); /** * Evict data from all query regions. * * @return void */ public function evictQueryRegions(); /** * Get query cache by region name or create a new one if none exist. * * @param string|null $regionName Query cache region name, or default query cache if the region name is NULL. * * @return QueryCache The Query Cache associated with the region name. */ public function getQueryCache($regionName = null); } orm/lib/Doctrine/ORM/Configuration.php 0000644 00000105201 15120025736 0013633 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BadMethodCallException; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\CachedReader; use Doctrine\Common\Annotations\SimpleAnnotationReader; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache as CacheDriver; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Common\Persistence\PersistentObject; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\CacheConfiguration; use Doctrine\ORM\Cache\Exception\CacheException; use Doctrine\ORM\Cache\Exception\MetadataCacheNotConfigured; use Doctrine\ORM\Cache\Exception\MetadataCacheUsesNonPersistentCache; use Doctrine\ORM\Cache\Exception\QueryCacheNotConfigured; use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache; use Doctrine\ORM\Exception\InvalidEntityRepository; use Doctrine\ORM\Exception\NamedNativeQueryNotFound; use Doctrine\ORM\Exception\NamedQueryNotFound; use Doctrine\ORM\Exception\NotSupported; use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating; use Doctrine\ORM\Exception\UnknownEntityNamespace; use Doctrine\ORM\Internal\Hydration\AbstractHydrator; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\DefaultEntityListenerResolver; use Doctrine\ORM\Mapping\DefaultNamingStrategy; use Doctrine\ORM\Mapping\DefaultQuoteStrategy; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\EntityListenerResolver; use Doctrine\ORM\Mapping\NamingStrategy; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Mapping\TypedFieldMapper; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\Filter\SQLFilter; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\Reflection\RuntimeReflectionProperty; use LogicException; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\VarExporter\LazyGhostTrait; use function class_exists; use function is_a; use function method_exists; use function sprintf; use function strtolower; use function trait_exists; use function trim; /** * Configuration container for all configuration options of Doctrine. * It combines all configuration options from DBAL & ORM. * * Internal note: When adding a new configuration option just write a getter/setter pair. * * @psalm-import-type AutogenerateMode from ProxyFactory */ class Configuration extends \Doctrine\DBAL\Configuration { /** @var mixed[] */ protected $_attributes = []; /** * Sets the directory where Doctrine generates any necessary proxy class files. * * @param string $dir * * @return void */ public function setProxyDir($dir) { $this->_attributes['proxyDir'] = $dir; } /** * Gets the directory where Doctrine generates any necessary proxy class files. * * @return string|null */ public function getProxyDir() { return $this->_attributes['proxyDir'] ?? null; } /** * Gets the strategy for automatically generating proxy classes. * * @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory. * @psalm-return AutogenerateMode */ public function getAutoGenerateProxyClasses() { return $this->_attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS; } /** * Sets the strategy for automatically generating proxy classes. * * @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory. * @psalm-param bool|AutogenerateMode $autoGenerate * True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER. * * @return void */ public function setAutoGenerateProxyClasses($autoGenerate) { $this->_attributes['autoGenerateProxyClasses'] = (int) $autoGenerate; } /** * Gets the namespace where proxy classes reside. * * @return string|null */ public function getProxyNamespace() { return $this->_attributes['proxyNamespace'] ?? null; } /** * Sets the namespace where proxy classes reside. * * @param string $ns * * @return void */ public function setProxyNamespace($ns) { $this->_attributes['proxyNamespace'] = $ns; } /** * Sets the cache driver implementation that is used for metadata caching. * * @return void * * @todo Force parameter to be a Closure to ensure lazy evaluation * (as soon as a metadata cache is in effect, the driver never needs to initialize). */ public function setMetadataDriverImpl(MappingDriver $driverImpl) { $this->_attributes['metadataDriverImpl'] = $driverImpl; } /** * Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader * is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported. * * @deprecated Use {@see ORMSetup::createDefaultAnnotationDriver()} instead. * * @param string|string[] $paths * @param bool $useSimpleAnnotationReader * @psalm-param string|list<string> $paths * * @return AnnotationDriver */ public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9443', '%s is deprecated, call %s::createDefaultAnnotationDriver() instead.', __METHOD__, ORMSetup::class ); if (! class_exists(AnnotationReader::class)) { throw new LogicException( 'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library' . ' is not installed. Please run "composer require doctrine/annotations" or choose a different' . ' metadata driver.' ); } if ($useSimpleAnnotationReader) { if (! class_exists(SimpleAnnotationReader::class)) { throw new BadMethodCallException( 'SimpleAnnotationReader has been removed in doctrine/annotations 2.' . ' Downgrade to version 1 or set $useSimpleAnnotationReader to false.' ); } // Register the ORM Annotations in the AnnotationRegistry $reader = new SimpleAnnotationReader(); $reader->addNamespace('Doctrine\ORM\Mapping'); } else { $reader = new AnnotationReader(); } if (class_exists(ArrayCache::class) && class_exists(CachedReader::class)) { $reader = new CachedReader($reader, new ArrayCache()); } return new AnnotationDriver( $reader, (array) $paths ); } /** * @deprecated No replacement planned. * * Adds a namespace under a certain alias. * * @param string $alias * @param string $namespace * * @return void */ public function addEntityNamespace($alias, $namespace) { if (class_exists(PersistentObject::class)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.', $alias ); } else { throw NotSupported::createForPersistence3(sprintf( 'Using short namespace alias "%s" by calling %s', $alias, __METHOD__ )); } $this->_attributes['entityNamespaces'][$alias] = $namespace; } /** * Resolves a registered namespace alias to the full namespace. * * @param string $entityNamespaceAlias * * @return string * * @throws UnknownEntityNamespace */ public function getEntityNamespace($entityNamespaceAlias) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Entity short namespace aliases such as "%s" are deprecated, use ::class constant instead.', $entityNamespaceAlias ); if (! isset($this->_attributes['entityNamespaces'][$entityNamespaceAlias])) { throw UnknownEntityNamespace::fromNamespaceAlias($entityNamespaceAlias); } return trim($this->_attributes['entityNamespaces'][$entityNamespaceAlias], '\\'); } /** * Sets the entity alias map. * * @psalm-param array<string, string> $entityNamespaces * * @return void */ public function setEntityNamespaces(array $entityNamespaces) { $this->_attributes['entityNamespaces'] = $entityNamespaces; } /** * Retrieves the list of registered entity namespace aliases. * * @psalm-return array<string, string> */ public function getEntityNamespaces() { return $this->_attributes['entityNamespaces']; } /** * Gets the cache driver implementation that is used for the mapping metadata. * * @return MappingDriver|null */ public function getMetadataDriverImpl() { return $this->_attributes['metadataDriverImpl'] ?? null; } /** * Gets the cache driver implementation that is used for query result caching. */ public function getResultCache(): ?CacheItemPoolInterface { // Compatibility with DBAL 2 if (! method_exists(parent::class, 'getResultCache')) { $cacheImpl = $this->getResultCacheImpl(); return $cacheImpl ? CacheAdapter::wrap($cacheImpl) : null; } return parent::getResultCache(); } /** * Sets the cache driver implementation that is used for query result caching. */ public function setResultCache(CacheItemPoolInterface $cache): void { // Compatibility with DBAL 2 if (! method_exists(parent::class, 'setResultCache')) { $this->setResultCacheImpl(DoctrineProvider::wrap($cache)); return; } parent::setResultCache($cache); } /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * * @deprecated Call {@see getQueryCache()} instead. * * @return CacheDriver|null */ public function getQueryCacheImpl() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9002', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getQueryCache() instead.', __METHOD__ ); return $this->_attributes['queryCacheImpl'] ?? null; } /** * Sets the cache driver implementation that is used for the query cache (SQL cache). * * @deprecated Call {@see setQueryCache()} instead. * * @return void */ public function setQueryCacheImpl(CacheDriver $cacheImpl) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9002', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use setQueryCache() instead.', __METHOD__ ); $this->_attributes['queryCache'] = CacheAdapter::wrap($cacheImpl); $this->_attributes['queryCacheImpl'] = $cacheImpl; } /** * Gets the cache driver implementation that is used for the query cache (SQL cache). */ public function getQueryCache(): ?CacheItemPoolInterface { return $this->_attributes['queryCache'] ?? null; } /** * Sets the cache driver implementation that is used for the query cache (SQL cache). */ public function setQueryCache(CacheItemPoolInterface $cache): void { $this->_attributes['queryCache'] = $cache; $this->_attributes['queryCacheImpl'] = DoctrineProvider::wrap($cache); } /** * Gets the cache driver implementation that is used for the hydration cache (SQL cache). * * @deprecated Call {@see getHydrationCache()} instead. * * @return CacheDriver|null */ public function getHydrationCacheImpl() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9002', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getHydrationCache() instead.', __METHOD__ ); return $this->_attributes['hydrationCacheImpl'] ?? null; } /** * Sets the cache driver implementation that is used for the hydration cache (SQL cache). * * @deprecated Call {@see setHydrationCache()} instead. * * @return void */ public function setHydrationCacheImpl(CacheDriver $cacheImpl) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9002', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use setHydrationCache() instead.', __METHOD__ ); $this->_attributes['hydrationCache'] = CacheAdapter::wrap($cacheImpl); $this->_attributes['hydrationCacheImpl'] = $cacheImpl; } public function getHydrationCache(): ?CacheItemPoolInterface { return $this->_attributes['hydrationCache'] ?? null; } public function setHydrationCache(CacheItemPoolInterface $cache): void { $this->_attributes['hydrationCache'] = $cache; $this->_attributes['hydrationCacheImpl'] = DoctrineProvider::wrap($cache); } /** * Gets the cache driver implementation that is used for metadata caching. * * @deprecated Deprecated in favor of getMetadataCache * * @return CacheDriver|null */ public function getMetadataCacheImpl() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8650', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getMetadataCache() instead.', __METHOD__ ); if (isset($this->_attributes['metadataCacheImpl'])) { return $this->_attributes['metadataCacheImpl']; } return isset($this->_attributes['metadataCache']) ? DoctrineProvider::wrap($this->_attributes['metadataCache']) : null; } /** * Sets the cache driver implementation that is used for metadata caching. * * @deprecated Deprecated in favor of setMetadataCache * * @return void */ public function setMetadataCacheImpl(CacheDriver $cacheImpl) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8650', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use setMetadataCache() instead.', __METHOD__ ); $this->_attributes['metadataCacheImpl'] = $cacheImpl; $this->_attributes['metadataCache'] = CacheAdapter::wrap($cacheImpl); } public function getMetadataCache(): ?CacheItemPoolInterface { return $this->_attributes['metadataCache'] ?? null; } public function setMetadataCache(CacheItemPoolInterface $cache): void { $this->_attributes['metadataCache'] = $cache; $this->_attributes['metadataCacheImpl'] = DoctrineProvider::wrap($cache); } /** * Adds a named DQL query to the configuration. * * @param string $name The name of the query. * @param string $dql The DQL query string. * * @return void */ public function addNamedQuery($name, $dql) { $this->_attributes['namedQueries'][$name] = $dql; } /** * Gets a previously registered named DQL query. * * @param string $name The name of the query. * * @return string The DQL query. * * @throws NamedQueryNotFound */ public function getNamedQuery($name) { if (! isset($this->_attributes['namedQueries'][$name])) { throw NamedQueryNotFound::fromName($name); } return $this->_attributes['namedQueries'][$name]; } /** * Adds a named native query to the configuration. * * @param string $name The name of the query. * @param string $sql The native SQL query string. * @param Query\ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query. * * @return void */ public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm) { $this->_attributes['namedNativeQueries'][$name] = [$sql, $rsm]; } /** * Gets the components of a previously registered named native query. * * @param string $name The name of the query. * * @return mixed[] * @psalm-return array{string, ResultSetMapping} A tuple with the first element being the SQL string and the second * element being the ResultSetMapping. * * @throws NamedNativeQueryNotFound */ public function getNamedNativeQuery($name) { if (! isset($this->_attributes['namedNativeQueries'][$name])) { throw NamedNativeQueryNotFound::fromName($name); } return $this->_attributes['namedNativeQueries'][$name]; } /** * Ensures that this Configuration instance contains settings that are * suitable for a production environment. * * @deprecated * * @return void * * @throws ProxyClassesAlwaysRegenerating * @throws CacheException If a configuration setting has a value that is not * suitable for a production environment. */ public function ensureProductionSettings() { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9074', '%s is deprecated', __METHOD__ ); $queryCacheImpl = $this->getQueryCacheImpl(); if (! $queryCacheImpl) { throw QueryCacheNotConfigured::create(); } if ($queryCacheImpl instanceof ArrayCache) { throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl); } if ($this->getAutoGenerateProxyClasses() !== ProxyFactory::AUTOGENERATE_NEVER) { throw ProxyClassesAlwaysRegenerating::create(); } if (! $this->getMetadataCache()) { throw MetadataCacheNotConfigured::create(); } $metadataCacheImpl = $this->getMetadataCacheImpl(); if ($metadataCacheImpl instanceof ArrayCache) { throw MetadataCacheUsesNonPersistentCache::fromDriver($metadataCacheImpl); } } /** * Registers a custom DQL function that produces a string value. * Such a function can then be used in any DQL statement in any place where string * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name Function name. * @param class-string|callable $className Class name or a callable that returns the function. * @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className * * @return void */ public function addCustomStringFunction($name, $className) { $this->_attributes['customStringFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom string DQL function. * * @param string $name * * @return string|callable|null * @psalm-return class-string<FunctionNode>|callable(string):FunctionNode|null */ public function getCustomStringFunction($name) { $name = strtolower($name); return $this->_attributes['customStringFunctions'][$name] ?? null; } /** * Sets a map of custom DQL string functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added string functions are discarded. * * @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions The map of custom * DQL string functions. * * @return void */ public function setCustomStringFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomStringFunction($name, $className); } } /** * Registers a custom DQL function that produces a numeric value. * Such a function can then be used in any DQL statement in any place where numeric * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name Function name. * @param class-string|callable $className Class name or a callable that returns the function. * @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className * * @return void */ public function addCustomNumericFunction($name, $className) { $this->_attributes['customNumericFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom numeric DQL function. * * @param string $name * * @return string|callable|null * @psalm-return class-string|callable|null */ public function getCustomNumericFunction($name) { $name = strtolower($name); return $this->_attributes['customNumericFunctions'][$name] ?? null; } /** * Sets a map of custom DQL numeric functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added numeric functions are discarded. * * @psalm-param array<string, class-string> $functions The map of custom * DQL numeric functions. * * @return void */ public function setCustomNumericFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomNumericFunction($name, $className); } } /** * Registers a custom DQL function that produces a date/time value. * Such a function can then be used in any DQL statement in any place where date/time * functions are allowed. * * DQL function names are case-insensitive. * * @param string $name Function name. * @param string|callable $className Class name or a callable that returns the function. * @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className * * @return void */ public function addCustomDatetimeFunction($name, $className) { $this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className; } /** * Gets the implementation class name of a registered custom date/time DQL function. * * @param string $name * * @return string|callable|null * @psalm-return class-string|callable|null */ public function getCustomDatetimeFunction($name) { $name = strtolower($name); return $this->_attributes['customDatetimeFunctions'][$name] ?? null; } /** * Sets a map of custom DQL date/time functions. * * Keys must be function names and values the FQCN of the implementing class. * The function names will be case-insensitive in DQL. * * Any previously added date/time functions are discarded. * * @param array $functions The map of custom DQL date/time functions. * @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions * * @return void */ public function setCustomDatetimeFunctions(array $functions) { foreach ($functions as $name => $className) { $this->addCustomDatetimeFunction($name, $className); } } /** * Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion. */ public function setTypedFieldMapper(?TypedFieldMapper $typedFieldMapper): void { $this->_attributes['typedFieldMapper'] = $typedFieldMapper; } /** * Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion. */ public function getTypedFieldMapper(): ?TypedFieldMapper { return $this->_attributes['typedFieldMapper'] ?? null; } /** * Sets the custom hydrator modes in one pass. * * @param array<string, class-string<AbstractHydrator>> $modes An array of ($modeName => $hydrator). * * @return void */ public function setCustomHydrationModes($modes) { $this->_attributes['customHydrationModes'] = []; foreach ($modes as $modeName => $hydrator) { $this->addCustomHydrationMode($modeName, $hydrator); } } /** * Gets the hydrator class for the given hydration mode name. * * @param string $modeName The hydration mode name. * * @return string|null The hydrator class name. * @psalm-return class-string<AbstractHydrator>|null */ public function getCustomHydrationMode($modeName) { return $this->_attributes['customHydrationModes'][$modeName] ?? null; } /** * Adds a custom hydration mode. * * @param string $modeName The hydration mode name. * @param string $hydrator The hydrator class name. * @psalm-param class-string<AbstractHydrator> $hydrator * * @return void */ public function addCustomHydrationMode($modeName, $hydrator) { $this->_attributes['customHydrationModes'][$modeName] = $hydrator; } /** * Sets a class metadata factory. * * @param string $cmfName * @psalm-param class-string $cmfName * * @return void */ public function setClassMetadataFactoryName($cmfName) { $this->_attributes['classMetadataFactoryName'] = $cmfName; } /** * @return string * @psalm-return class-string */ public function getClassMetadataFactoryName() { if (! isset($this->_attributes['classMetadataFactoryName'])) { $this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class; } return $this->_attributes['classMetadataFactoryName']; } /** * Adds a filter to the list of possible filters. * * @param string $name The name of the filter. * @param string $className The class name of the filter. * @psalm-param class-string<SQLFilter> $className * * @return void */ public function addFilter($name, $className) { $this->_attributes['filters'][$name] = $className; } /** * Gets the class name for a given filter name. * * @param string $name The name of the filter. * * @return string|null The class name of the filter, or null if it is not * defined. * @psalm-return class-string<SQLFilter>|null */ public function getFilterClassName($name) { return $this->_attributes['filters'][$name] ?? null; } /** * Sets default repository class. * * @param string $className * @psalm-param class-string<EntityRepository> $className * * @return void * * @throws InvalidEntityRepository If $classname is not an ObjectRepository. */ public function setDefaultRepositoryClassName($className) { if (! class_exists($className) || ! is_a($className, ObjectRepository::class, true)) { throw InvalidEntityRepository::fromClassName($className); } if (! is_a($className, EntityRepository::class, true)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9533', 'Configuring %s as default repository class is deprecated because it does not extend %s.', $className, EntityRepository::class ); } $this->_attributes['defaultRepositoryClassName'] = $className; } /** * Get default repository class. * * @return string * @psalm-return class-string<EntityRepository> */ public function getDefaultRepositoryClassName() { return $this->_attributes['defaultRepositoryClassName'] ?? EntityRepository::class; } /** * Sets naming strategy. * * @return void */ public function setNamingStrategy(NamingStrategy $namingStrategy) { $this->_attributes['namingStrategy'] = $namingStrategy; } /** * Gets naming strategy.. * * @return NamingStrategy */ public function getNamingStrategy() { if (! isset($this->_attributes['namingStrategy'])) { $this->_attributes['namingStrategy'] = new DefaultNamingStrategy(); } return $this->_attributes['namingStrategy']; } /** * Sets quote strategy. * * @return void */ public function setQuoteStrategy(QuoteStrategy $quoteStrategy) { $this->_attributes['quoteStrategy'] = $quoteStrategy; } /** * Gets quote strategy. * * @return QuoteStrategy */ public function getQuoteStrategy() { if (! isset($this->_attributes['quoteStrategy'])) { $this->_attributes['quoteStrategy'] = new DefaultQuoteStrategy(); } return $this->_attributes['quoteStrategy']; } /** * Set the entity listener resolver. * * @return void */ public function setEntityListenerResolver(EntityListenerResolver $resolver) { $this->_attributes['entityListenerResolver'] = $resolver; } /** * Get the entity listener resolver. * * @return EntityListenerResolver */ public function getEntityListenerResolver() { if (! isset($this->_attributes['entityListenerResolver'])) { $this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver(); } return $this->_attributes['entityListenerResolver']; } /** * Set the entity repository factory. * * @return void */ public function setRepositoryFactory(RepositoryFactory $repositoryFactory) { $this->_attributes['repositoryFactory'] = $repositoryFactory; } /** * Get the entity repository factory. * * @return RepositoryFactory */ public function getRepositoryFactory() { return $this->_attributes['repositoryFactory'] ?? new DefaultRepositoryFactory(); } /** @return bool */ public function isSecondLevelCacheEnabled() { return $this->_attributes['isSecondLevelCacheEnabled'] ?? false; } /** * @param bool $flag * * @return void */ public function setSecondLevelCacheEnabled($flag = true) { $this->_attributes['isSecondLevelCacheEnabled'] = (bool) $flag; } /** @return void */ public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig) { $this->_attributes['secondLevelCacheConfiguration'] = $cacheConfig; } /** @return CacheConfiguration|null */ public function getSecondLevelCacheConfiguration() { if (! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) { $this->_attributes['secondLevelCacheConfiguration'] = new CacheConfiguration(); } return $this->_attributes['secondLevelCacheConfiguration'] ?? null; } /** * Returns query hints, which will be applied to every query in application * * @psalm-return array<string, mixed> */ public function getDefaultQueryHints() { return $this->_attributes['defaultQueryHints'] ?? []; } /** * Sets array of query hints, which will be applied to every query in application * * @psalm-param array<string, mixed> $defaultQueryHints * * @return void */ public function setDefaultQueryHints(array $defaultQueryHints) { $this->_attributes['defaultQueryHints'] = $defaultQueryHints; } /** * Gets the value of a default query hint. If the hint name is not recognized, FALSE is returned. * * @param string $name The name of the hint. * * @return mixed The value of the hint or FALSE, if the hint name is not recognized. */ public function getDefaultQueryHint($name) { return $this->_attributes['defaultQueryHints'][$name] ?? false; } /** * Sets a default query hint. If the hint name is not recognized, it is silently ignored. * * @param string $name The name of the hint. * @param mixed $value The value of the hint. * * @return void */ public function setDefaultQueryHint($name, $value) { $this->_attributes['defaultQueryHints'][$name] = $value; } /** * Gets a list of entity class names to be ignored by the SchemaTool * * @return list<class-string> */ public function getSchemaIgnoreClasses(): array { return $this->_attributes['schemaIgnoreClasses'] ?? []; } /** * Sets a list of entity class names to be ignored by the SchemaTool * * @param list<class-string> $schemaIgnoreClasses List of entity class names */ public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void { $this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses; } public function isLazyGhostObjectEnabled(): bool { return $this->_attributes['isLazyGhostObjectEnabled'] ?? false; } public function setLazyGhostObjectEnabled(bool $flag): void { if ($flag && ! trait_exists(LazyGhostTrait::class)) { throw new LogicException( 'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library' . ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".' ); } if ($flag && ! class_exists(RuntimeReflectionProperty::class)) { throw new LogicException( 'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library' . ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".' ); } $this->_attributes['isLazyGhostObjectEnabled'] = $flag; } } orm/lib/Doctrine/ORM/EntityManager.php 0000644 00000076263 15120025736 0013612 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BackedEnum; use BadMethodCallException; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\EventManager; use Doctrine\Common\Persistence\PersistentObject; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\LockMode; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Exception\EntityManagerClosed; use Doctrine\ORM\Exception\InvalidHydrationMode; use Doctrine\ORM\Exception\MismatchedEventManager; use Doctrine\ORM\Exception\MissingIdentifierField; use Doctrine\ORM\Exception\MissingMappingDriverImplementation; use Doctrine\ORM\Exception\NotSupported; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\UnrecognizedIdentifierFields; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\Mapping\MappingException; use Doctrine\Persistence\ObjectRepository; use InvalidArgumentException; use Throwable; use function array_keys; use function class_exists; use function get_debug_type; use function gettype; use function is_array; use function is_callable; use function is_object; use function is_string; use function ltrim; use function sprintf; use function strpos; /** * The EntityManager is the central access point to ORM functionality. * * It is a facade to all different ORM subsystems such as UnitOfWork, * Query Language and Repository API. Instantiation is done through * the static create() method. The quickest way to obtain a fully * configured EntityManager is: * * use Doctrine\ORM\Tools\ORMSetup; * use Doctrine\ORM\EntityManager; * * $paths = ['/path/to/entity/mapping/files']; * * $config = ORMSetup::createAttributeMetadataConfiguration($paths); * $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config); * $entityManager = new EntityManager($connection, $config); * * For more information see * {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html} * * You should never attempt to inherit from the EntityManager: Inheritance * is not a valid extension point for the EntityManager. Instead you * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator} * and wrap your entity manager in a decorator. * * @final */ class EntityManager implements EntityManagerInterface { /** * The used Configuration. * * @var Configuration */ private $config; /** * The database connection used by the EntityManager. * * @var Connection */ private $conn; /** * The metadata factory, used to retrieve the ORM metadata of entity classes. * * @var ClassMetadataFactory */ private $metadataFactory; /** * The UnitOfWork used to coordinate object-level transactions. * * @var UnitOfWork */ private $unitOfWork; /** * The event manager that is the central point of the event system. * * @var EventManager */ private $eventManager; /** * The proxy factory used to create dynamic proxies. * * @var ProxyFactory */ private $proxyFactory; /** * The repository factory used to create dynamic repositories. * * @var RepositoryFactory */ private $repositoryFactory; /** * The expression builder instance used to generate query expressions. * * @var Expr|null */ private $expressionBuilder; /** * Whether the EntityManager is closed or not. * * @var bool */ private $closed = false; /** * Collection of query filters. * * @var FilterCollection|null */ private $filterCollection; /** * The second level cache regions API. * * @var Cache|null */ private $cache; /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. */ public function __construct(Connection $conn, Configuration $config, ?EventManager $eventManager = null) { if (! $config->getMetadataDriverImpl()) { throw MissingMappingDriverImplementation::create(); } $this->conn = $conn; $this->config = $config; $this->eventManager = $eventManager ?? $conn->getEventManager(); $metadataFactoryClassName = $config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName(); $this->metadataFactory->setEntityManager($this); $this->configureMetadataCache(); $this->repositoryFactory = $config->getRepositoryFactory(); $this->unitOfWork = new UnitOfWork($this); $this->proxyFactory = new ProxyFactory( $this, $config->getProxyDir(), $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses() ); if ($config->isSecondLevelCacheEnabled()) { $cacheConfig = $config->getSecondLevelCacheConfiguration(); $cacheFactory = $cacheConfig->getCacheFactory(); $this->cache = $cacheFactory->createCache($this); } } /** * {@inheritDoc} */ public function getConnection() { return $this->conn; } /** * Gets the metadata factory used to gather the metadata of classes. * * @return ClassMetadataFactory */ public function getMetadataFactory() { return $this->metadataFactory; } /** * {@inheritDoc} */ public function getExpressionBuilder() { if ($this->expressionBuilder === null) { $this->expressionBuilder = new Query\Expr(); } return $this->expressionBuilder; } /** * {@inheritDoc} */ public function beginTransaction() { $this->conn->beginTransaction(); } /** * {@inheritDoc} */ public function getCache() { return $this->cache; } /** * {@inheritDoc} */ public function transactional($func) { if (! is_callable($func)) { throw new InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"'); } $this->conn->beginTransaction(); try { $return = $func($this); $this->flush(); $this->conn->commit(); return $return ?: true; } catch (Throwable $e) { $this->close(); $this->conn->rollBack(); throw $e; } } /** * {@inheritDoc} */ public function wrapInTransaction(callable $func) { $this->conn->beginTransaction(); try { $return = $func($this); $this->flush(); $this->conn->commit(); return $return; } catch (Throwable $e) { $this->close(); $this->conn->rollBack(); throw $e; } } /** * {@inheritDoc} */ public function commit() { $this->conn->commit(); } /** * {@inheritDoc} */ public function rollback() { $this->conn->rollBack(); } /** * Returns the ORM metadata descriptor for a class. * * The class name must be the fully-qualified class name without a leading backslash * (as it is returned by get_class($obj)) or an aliased class name. * * Examples: * MyProject\Domain\User * sales:PriceRequest * * Internal note: Performance-sensitive method. * * {@inheritDoc} */ public function getClassMetadata($className) { return $this->metadataFactory->getMetadataFor($className); } /** * {@inheritDoc} */ public function createQuery($dql = '') { $query = new Query($this); if (! empty($dql)) { $query->setDQL($dql); } return $query; } /** * {@inheritDoc} */ public function createNamedQuery($name) { return $this->createQuery($this->config->getNamedQuery($name)); } /** * {@inheritDoc} */ public function createNativeQuery($sql, ResultSetMapping $rsm) { $query = new NativeQuery($this); $query->setSQL($sql); $query->setResultSetMapping($rsm); return $query; } /** * {@inheritDoc} */ public function createNamedNativeQuery($name) { [$sql, $rsm] = $this->config->getNamedNativeQuery($name); return $this->createNativeQuery($sql, $rsm); } /** * {@inheritDoc} */ public function createQueryBuilder() { return new QueryBuilder($this); } /** * Flushes all changes to objects that have been queued up to now to the database. * This effectively synchronizes the in-memory state of managed objects with the * database. * * If an entity is explicitly passed to this method only this entity and * the cascade-persist semantics + scheduled inserts/removals are synchronized. * * @param object|mixed[]|null $entity * * @return void * * @throws OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. * @throws ORMException */ public function flush($entity = null) { if ($entity !== null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8459', 'Calling %s() with any arguments to flush specific entities is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__ ); } $this->errorIfClosed(); $this->unitOfWork->commit($entity); } /** * Finds an Entity by its identifier. * * @param string $className The class name of the entity to find. * @param mixed $id The identity of the entity to find. * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants * or NULL if no specific lock mode should be used * during the search. * @param int|null $lockVersion The version of the entity to find when using * optimistic locking. * @psalm-param class-string<T> $className * @psalm-param LockMode::*|null $lockMode * * @return object|null The entity instance or NULL if the entity can not be found. * @psalm-return ?T * * @throws OptimisticLockException * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws ORMException * * @template T */ public function find($className, $id, $lockMode = null, $lockVersion = null) { $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\')); if ($lockMode !== null) { $this->checkLockRequirements($lockMode, $class); } if (! is_array($id)) { if ($class->isIdentifierComposite) { throw ORMInvalidArgumentException::invalidCompositeIdentifier(); } $id = [$class->identifier[0] => $id]; } foreach ($id as $i => $value) { if (is_object($value)) { $className = ClassUtils::getClass($value); if ($this->metadataFactory->hasMetadataFor($className)) { $id[$i] = $this->unitOfWork->getSingleIdentifierValue($value); if ($id[$i] === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($className); } } } } $sortedId = []; foreach ($class->identifier as $identifier) { if (! isset($id[$identifier])) { throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); } if ($id[$identifier] instanceof BackedEnum) { $sortedId[$identifier] = $id[$identifier]->value; } else { $sortedId[$identifier] = $id[$identifier]; } unset($id[$identifier]); } if ($id) { throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); } $unitOfWork = $this->getUnitOfWork(); $entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName); // Check identity map first if ($entity !== false) { if (! ($entity instanceof $class->name)) { return null; } switch (true) { case $lockMode === LockMode::OPTIMISTIC: $this->lock($entity, $lockMode, $lockVersion); break; case $lockMode === LockMode::NONE: case $lockMode === LockMode::PESSIMISTIC_READ: case $lockMode === LockMode::PESSIMISTIC_WRITE: $persister = $unitOfWork->getEntityPersister($class->name); $persister->refresh($sortedId, $entity, $lockMode); break; } return $entity; // Hit! } $persister = $unitOfWork->getEntityPersister($class->name); switch (true) { case $lockMode === LockMode::OPTIMISTIC: $entity = $persister->load($sortedId); if ($entity !== null) { $unitOfWork->lock($entity, $lockMode, $lockVersion); } return $entity; case $lockMode === LockMode::PESSIMISTIC_READ: case $lockMode === LockMode::PESSIMISTIC_WRITE: return $persister->load($sortedId, null, null, [], $lockMode); default: return $persister->loadById($sortedId); } } /** * {@inheritDoc} */ public function getReference($entityName, $id) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); if (! is_array($id)) { $id = [$class->identifier[0] => $id]; } $sortedId = []; foreach ($class->identifier as $identifier) { if (! isset($id[$identifier])) { throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name); } $sortedId[$identifier] = $id[$identifier]; unset($id[$identifier]); } if ($id) { throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id)); } $entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName); // Check identity map first, if its already in there just return it. if ($entity !== false) { return $entity instanceof $class->name ? $entity : null; } if ($class->subClasses) { return $this->find($entityName, $sortedId); } $entity = $this->proxyFactory->getProxy($class->name, $sortedId); $this->unitOfWork->registerManaged($entity, $sortedId, []); return $entity; } /** * {@inheritDoc} */ public function getPartialReference($entityName, $identifier) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); $entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName); // Check identity map first, if its already in there just return it. if ($entity !== false) { return $entity instanceof $class->name ? $entity : null; } if (! is_array($identifier)) { $identifier = [$class->identifier[0] => $identifier]; } $entity = $class->newInstance(); $class->setIdentifierValues($entity, $identifier); $this->unitOfWork->registerManaged($entity, $identifier, []); $this->unitOfWork->markReadOnly($entity); return $entity; } /** * Clears the EntityManager. All entities that are currently managed * by this EntityManager become detached. * * @param string|null $entityName if given, only entities of this type will get detached * * @return void * * @throws ORMInvalidArgumentException If a non-null non-string value is given. * @throws MappingException If a $entityName is given, but that entity is not * found in the mappings. */ public function clear($entityName = null) { if ($entityName !== null && ! is_string($entityName)) { throw ORMInvalidArgumentException::invalidEntityName($entityName); } if ($entityName !== null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8460', 'Calling %s() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__ ); } $this->unitOfWork->clear( $entityName === null ? null : $this->metadataFactory->getMetadataFor($entityName)->getName() ); } /** * {@inheritDoc} */ public function close() { $this->clear(); $this->closed = true; } /** * Tells the EntityManager to make an instance managed and persistent. * * The entity will be entered into the database at or before transaction * commit or as a result of the flush operation. * * NOTE: The persist operation always considers entities that are not yet known to * this EntityManager as NEW. Do not pass detached entities to the persist operation. * * @param object $entity The instance to make managed and persistent. * * @return void * * @throws ORMInvalidArgumentException * @throws ORMException */ public function persist($entity) { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity); } $this->errorIfClosed(); $this->unitOfWork->persist($entity); } /** * Removes an entity instance. * * A removed entity will be removed from the database at or before transaction commit * or as a result of the flush operation. * * @param object $entity The entity instance to remove. * * @return void * * @throws ORMInvalidArgumentException * @throws ORMException */ public function remove($entity) { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity); } $this->errorIfClosed(); $this->unitOfWork->remove($entity); } /** * Refreshes the persistent state of an entity from the database, * overriding any local changes that have not yet been persisted. * * @param object $entity The entity to refresh * @psalm-param LockMode::*|null $lockMode * * @return void * * @throws ORMInvalidArgumentException * @throws ORMException * @throws TransactionRequiredException */ public function refresh($entity, ?int $lockMode = null) { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity); } $this->errorIfClosed(); $this->unitOfWork->refresh($entity, $lockMode); } /** * Detaches an entity from the EntityManager, causing a managed entity to * become detached. Unflushed changes made to the entity if any * (including removal of the entity), will not be synchronized to the database. * Entities which previously referenced the detached entity will continue to * reference it. * * @param object $entity The entity to detach. * * @return void * * @throws ORMInvalidArgumentException */ public function detach($entity) { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity); } $this->unitOfWork->detach($entity); } /** * Merges the state of a detached entity into the persistence context * of this EntityManager and returns the managed copy of the entity. * The entity passed to merge will not become associated/managed with this EntityManager. * * @deprecated 2.7 This method is being removed from the ORM and won't have any replacement * * @param object $entity The detached entity to merge into the persistence context. * * @return object The managed copy of the entity. * * @throws ORMInvalidArgumentException * @throws ORMException */ public function merge($entity) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8461', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0.', __METHOD__ ); if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity); } $this->errorIfClosed(); return $this->unitOfWork->merge($entity); } /** * {@inheritDoc} * * @psalm-return never */ public function copy($entity, $deep = false) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8462', 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0.', __METHOD__ ); throw new BadMethodCallException('Not implemented.'); } /** * {@inheritDoc} */ public function lock($entity, $lockMode, $lockVersion = null) { $this->unitOfWork->lock($entity, $lockMode, $lockVersion); } /** * Gets the repository for an entity class. * * @param string $entityName The name of the entity. * @psalm-param class-string<T> $entityName * * @return ObjectRepository|EntityRepository The repository class. * @psalm-return EntityRepository<T> * * @template T of object */ public function getRepository($entityName) { if (strpos($entityName, ':') !== false) { if (class_exists(PersistentObject::class)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.', $entityName ); } else { throw NotSupported::createForPersistence3(sprintf( 'Using short namespace alias "%s" when calling %s', $entityName, __METHOD__ )); } } $repository = $this->repositoryFactory->getRepository($this, $entityName); if (! $repository instanceof EntityRepository) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9533', 'Not returning an instance of %s from %s::getRepository() is deprecated and will cause a TypeError on 3.0.', EntityRepository::class, get_debug_type($this->repositoryFactory) ); } return $repository; } /** * Determines whether an entity instance is managed in this EntityManager. * * @param object $entity * * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise. */ public function contains($entity) { return $this->unitOfWork->isScheduledForInsert($entity) || $this->unitOfWork->isInIdentityMap($entity) && ! $this->unitOfWork->isScheduledForDelete($entity); } /** * {@inheritDoc} */ public function getEventManager() { return $this->eventManager; } /** * {@inheritDoc} */ public function getConfiguration() { return $this->config; } /** * Throws an exception if the EntityManager is closed or currently not active. * * @throws EntityManagerClosed If the EntityManager is closed. */ private function errorIfClosed(): void { if ($this->closed) { throw EntityManagerClosed::create(); } } /** * {@inheritDoc} */ public function isOpen() { return ! $this->closed; } /** * {@inheritDoc} */ public function getUnitOfWork() { return $this->unitOfWork; } /** * {@inheritDoc} */ public function getHydrator($hydrationMode) { return $this->newHydrator($hydrationMode); } /** * {@inheritDoc} */ public function newHydrator($hydrationMode) { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: return new Internal\Hydration\ObjectHydrator($this); case Query::HYDRATE_ARRAY: return new Internal\Hydration\ArrayHydrator($this); case Query::HYDRATE_SCALAR: return new Internal\Hydration\ScalarHydrator($this); case Query::HYDRATE_SINGLE_SCALAR: return new Internal\Hydration\SingleScalarHydrator($this); case Query::HYDRATE_SIMPLEOBJECT: return new Internal\Hydration\SimpleObjectHydrator($this); case Query::HYDRATE_SCALAR_COLUMN: return new Internal\Hydration\ScalarColumnHydrator($this); default: $class = $this->config->getCustomHydrationMode($hydrationMode); if ($class !== null) { return new $class($this); } } throw InvalidHydrationMode::fromMode((string) $hydrationMode); } /** * {@inheritDoc} */ public function getProxyFactory() { return $this->proxyFactory; } /** * {@inheritDoc} */ public function initializeObject($obj) { $this->unitOfWork->initializeObject($obj); } /** * Factory method to create EntityManager instances. * * @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection and call the constructor. * * @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance. * @param Configuration $config The Configuration instance to use. * @param EventManager|null $eventManager The EventManager instance to use. * @psalm-param array<string, mixed>|Connection $connection * * @return EntityManager The created EntityManager. * * @throws InvalidArgumentException * @throws ORMException */ public static function create($connection, Configuration $config, ?EventManager $eventManager = null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9961', '%s() is deprecated. To boostrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.', __METHOD__, DriverManager::class, self::class ); $connection = static::createConnection($connection, $config, $eventManager); return new EntityManager($connection, $config); } /** * Factory method to create Connection instances. * * @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection. * * @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance. * @param Configuration $config The Configuration instance to use. * @param EventManager|null $eventManager The EventManager instance to use. * @psalm-param array<string, mixed>|Connection $connection * * @return Connection * * @throws InvalidArgumentException * @throws ORMException */ protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9961', '%s() is deprecated, call %s::getConnection() instead.', __METHOD__, DriverManager::class ); if (is_array($connection)) { return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager()); } if (! $connection instanceof Connection) { throw new InvalidArgumentException( sprintf( 'Invalid $connection argument of type %s given%s.', get_debug_type($connection), is_object($connection) ? '' : ': "' . $connection . '"' ) ); } if ($eventManager !== null && $connection->getEventManager() !== $eventManager) { throw MismatchedEventManager::create(); } return $connection; } /** * {@inheritDoc} */ public function getFilters() { if ($this->filterCollection === null) { $this->filterCollection = new FilterCollection($this); } return $this->filterCollection; } /** * {@inheritDoc} */ public function isFiltersStateClean() { return $this->filterCollection === null || $this->filterCollection->isClean(); } /** * {@inheritDoc} */ public function hasFilters() { return $this->filterCollection !== null; } /** * @psalm-param LockMode::* $lockMode * * @throws OptimisticLockException * @throws TransactionRequiredException */ private function checkLockRequirements(int $lockMode, ClassMetadata $class): void { switch ($lockMode) { case LockMode::OPTIMISTIC: if (! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } break; case LockMode::PESSIMISTIC_READ: case LockMode::PESSIMISTIC_WRITE: if (! $this->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } } private function configureMetadataCache(): void { $metadataCache = $this->config->getMetadataCache(); if (! $metadataCache) { $this->configureLegacyMetadataCache(); return; } $this->metadataFactory->setCache($metadataCache); } private function configureLegacyMetadataCache(): void { $metadataCache = $this->config->getMetadataCacheImpl(); if (! $metadataCache) { return; } // Wrap doctrine/cache to provide PSR-6 interface $this->metadataFactory->setCache(CacheAdapter::wrap($metadataCache)); } } orm/lib/Doctrine/ORM/EntityManagerInterface.php 0000644 00000023075 15120025736 0015424 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BadMethodCallException; use DateTimeInterface; use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Internal\Hydration\AbstractHydrator; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ObjectManager; /** * EntityManager interface * * @method Mapping\ClassMetadataFactory getMetadataFactory() * @method mixed wrapInTransaction(callable $func) * @method void refresh(object $object, ?int $lockMode = null) */ interface EntityManagerInterface extends ObjectManager { /** * {@inheritDoc} * * @psalm-param class-string<T> $className * * @psalm-return EntityRepository<T> * * @template T of object */ public function getRepository($className); /** * Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled. * * @return Cache|null */ public function getCache(); /** * Gets the database connection object used by the EntityManager. * * @return Connection */ public function getConnection(); /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * * Example: * * <code> * $qb = $em->createQueryBuilder(); * $expr = $em->getExpressionBuilder(); * $qb->select('u')->from('User', 'u') * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); * </code> * * @return Expr */ public function getExpressionBuilder(); /** * Starts a transaction on the underlying database connection. * * @return void */ public function beginTransaction(); /** * Executes a function in a transaction. * * The function gets passed this EntityManager instance as an (optional) parameter. * * {@link flush} is invoked prior to transaction commit. * * If an exception occurs during execution of the function or flushing or transaction commit, * the transaction is rolled back, the EntityManager closed and the exception re-thrown. * * @deprecated 2.10 Use {@link wrapInTransaction} instead. * * @param callable $func The function to execute transactionally. * * @return mixed The non-empty value returned from the closure or true instead. */ public function transactional($func); /** * Executes a function in a transaction. * * The function gets passed this EntityManager instance as an (optional) parameter. * * {@link flush} is invoked prior to transaction commit. * * If an exception occurs during execution of the function or flushing or transaction commit, * the transaction is rolled back, the EntityManager closed and the exception re-thrown. * * @param callable(self): T $func The function to execute transactionally. * * @return T The value returned from the closure. * * @template T */ // public function wrapInTransaction(callable $func); /** * Commits a transaction on the underlying database connection. * * @return void */ public function commit(); /** * Performs a rollback on the underlying database connection. * * @return void */ public function rollback(); /** * Creates a new Query object. * * @param string $dql The DQL string. * * @return Query */ public function createQuery($dql = ''); /** * Creates a Query from a named query. * * @param string $name * * @return Query */ public function createNamedQuery($name); /** * Creates a native SQL query. * * @param string $sql * @param ResultSetMapping $rsm The ResultSetMapping to use. * * @return NativeQuery */ public function createNativeQuery($sql, ResultSetMapping $rsm); /** * Creates a NativeQuery from a named native query. * * @param string $name * * @return NativeQuery */ public function createNamedNativeQuery($name); /** * Create a QueryBuilder instance * * @return QueryBuilder */ public function createQueryBuilder(); /** * Gets a reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * @param string $entityName The name of the entity type. * @param mixed $id The entity identifier. * @psalm-param class-string<T> $entityName * * @return object|null The entity reference. * @psalm-return T|null * * @throws ORMException * * @template T */ public function getReference($entityName, $id); /** * Gets a partial reference to the entity identified by the given type and identifier * without actually loading it, if the entity is not yet loaded. * * The returned reference may be a partial object if the entity is not yet loaded/managed. * If it is a partial object it will not initialize the rest of the entity state on access. * Thus you can only ever safely access the identifier of an entity obtained through * this method. * * The use-cases for partial references involve maintaining bidirectional associations * without loading one side of the association or to update an entity without loading it. * Note, however, that in the latter case the original (persistent) entity data will * never be visible to the application (especially not event listeners) as it will * never be loaded in the first place. * * @param string $entityName The name of the entity type. * @param mixed $identifier The entity identifier. * @psalm-param class-string<T> $entityName * * @return object|null The (partial) entity reference * @psalm-return T|null * * @template T */ public function getPartialReference($entityName, $identifier); /** * Closes the EntityManager. All entities that are currently managed * by this EntityManager become detached. The EntityManager may no longer * be used after it is closed. * * @return void */ public function close(); /** * Creates a copy of the given entity. Can create a shallow or a deep copy. * * @deprecated 2.7 This method is being removed from the ORM and won't have any replacement * * @param object $entity The entity to copy. * @param bool $deep FALSE for a shallow copy, TRUE for a deep copy. * * @return object The new entity. * * @throws BadMethodCallException */ public function copy($entity, $deep = false); /** * Acquire a lock on the given entity. * * @param object $entity * @param int $lockMode * @param int|DateTimeInterface|null $lockVersion * @psalm-param LockMode::* $lockMode * * @return void * * @throws OptimisticLockException * @throws PessimisticLockException */ public function lock($entity, $lockMode, $lockVersion = null); /** * Gets the EventManager used by the EntityManager. * * @return EventManager */ public function getEventManager(); /** * Gets the Configuration used by the EntityManager. * * @return Configuration */ public function getConfiguration(); /** * Check if the Entity manager is open or closed. * * @return bool */ public function isOpen(); /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * * @return UnitOfWork */ public function getUnitOfWork(); /** * Gets a hydrator for the given hydration mode. * * This method caches the hydrator instances which is used for all queries that don't * selectively iterate over the result. * * @deprecated * * @param string|int $hydrationMode * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode * * @return AbstractHydrator */ public function getHydrator($hydrationMode); /** * Create a new instance for the given hydration mode. * * @param string|int $hydrationMode * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode * * @return AbstractHydrator * * @throws ORMException */ public function newHydrator($hydrationMode); /** * Gets the proxy factory used by the EntityManager to create entity proxies. * * @return ProxyFactory */ public function getProxyFactory(); /** * Gets the enabled filters. * * @return FilterCollection The active filter collection. */ public function getFilters(); /** * Checks whether the state of the filter collection is clean. * * @return bool True, if the filter collection is clean. */ public function isFiltersStateClean(); /** * Checks whether the Entity Manager has filters. * * @return bool True, if the EM has a filter collection. */ public function hasFilters(); /** * {@inheritDoc} * * @psalm-param string|class-string<T> $className * * @return Mapping\ClassMetadata * @psalm-return Mapping\ClassMetadata<T> * * @psalm-template T of object */ public function getClassMetadata($className); } orm/lib/Doctrine/ORM/EntityNotFoundException.php 0000644 00000002076 15120025736 0015642 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\ORM\Exception\ORMException; use function implode; use function sprintf; /** * Exception thrown when a Proxy fails to retrieve an Entity result. */ class EntityNotFoundException extends ORMException { /** * Static constructor. * * @param string $className * @param string[] $id * * @return self */ public static function fromClassNameAndIdentifier($className, array $id) { $ids = []; foreach ($id as $key => $value) { $ids[] = $key . '(' . $value . ')'; } return new self( 'Entity of type \'' . $className . '\'' . ($ids ? ' for IDs ' . implode(', ', $ids) : '') . ' was not found' ); } /** * Instance for which no identifier can be found */ public static function noIdentifierFound(string $className): self { return new self(sprintf( 'Unable to find "%s" entity identifier associated with the UnitOfWork', $className )); } } orm/lib/Doctrine/ORM/EntityRepository.php 0000644 00000026031 15120025736 0014403 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BadMethodCallException; use Doctrine\Common\Collections\AbstractLazyCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Selectable; use Doctrine\Common\Persistence\PersistentObject; use Doctrine\DBAL\LockMode; use Doctrine\Deprecations\Deprecation; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\InflectorFactory; use Doctrine\ORM\Exception\NotSupported; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall; use Doctrine\Persistence\ObjectRepository; use function array_slice; use function class_exists; use function lcfirst; use function sprintf; use function str_starts_with; use function substr; /** * An EntityRepository serves as a repository for entities with generic as well as * business specific methods for retrieving entities. * * This class is designed for inheritance and users can subclass this class to * write their own repositories with business-specific methods to locate entities. * * @template T of object * @template-implements Selectable<int,T> * @template-implements ObjectRepository<T> */ class EntityRepository implements ObjectRepository, Selectable { /** * @internal This property will be private in 3.0, call {@see getEntityName()} instead. * * @var string * @psalm-var class-string<T> */ protected $_entityName; /** * @internal This property will be private in 3.0, call {@see getEntityManager()} instead. * * @var EntityManagerInterface */ protected $_em; /** * @internal This property will be private in 3.0, call {@see getClassMetadata()} instead. * * @var ClassMetadata * @psalm-var ClassMetadata<T> */ protected $_class; /** @var Inflector|null */ private static $inflector; /** @psalm-param ClassMetadata<T> $class */ public function __construct(EntityManagerInterface $em, ClassMetadata $class) { $this->_entityName = $class->name; $this->_em = $em; $this->_class = $class; } /** * Creates a new QueryBuilder instance that is prepopulated for this entity name. * * @param string $alias * @param string|null $indexBy The index for the from. * * @return QueryBuilder */ public function createQueryBuilder($alias, $indexBy = null) { return $this->_em->createQueryBuilder() ->select($alias) ->from($this->_entityName, $alias, $indexBy); } /** * Creates a new result set mapping builder for this entity. * * The column naming strategy is "INCREMENT". * * @param string $alias * * @return ResultSetMappingBuilder */ public function createResultSetMappingBuilder($alias) { $rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT); $rsm->addRootEntityFromClassMetadata($this->_entityName, $alias); return $rsm; } /** * Creates a new Query instance based on a predefined metadata named query. * * @deprecated * * @param string $queryName * * @return Query */ public function createNamedQuery($queryName) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8592', 'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository', $queryName, $this->_class->name ); return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); } /** * Creates a native SQL query. * * @deprecated * * @param string $queryName * * @return NativeQuery */ public function createNativeNamedQuery($queryName) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8592', 'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository', $queryName, $this->_class->name ); $queryMapping = $this->_class->getNamedNativeQuery($queryName); $rsm = new Query\ResultSetMappingBuilder($this->_em); $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping); return $this->_em->createNativeQuery($queryMapping['query'], $rsm); } /** * Clears the repository, causing all managed entities to become detached. * * @deprecated 2.8 This method is being removed from the ORM and won't have any replacement * * @return void */ public function clear() { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8460', 'Calling %s() is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__ ); if (! class_exists(PersistentObject::class)) { throw NotSupported::createForPersistence3(sprintf( 'Partial clearing of entities for class %s', $this->_class->rootEntityName )); } $this->_em->clear($this->_class->rootEntityName); } /** * Finds an entity by its primary key / identifier. * * @param mixed $id The identifier. * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants * or NULL if no specific lock mode should be used * during the search. * @param int|null $lockVersion The lock version. * @psalm-param LockMode::*|null $lockMode * * @return object|null The entity instance or NULL if the entity can not be found. * @psalm-return ?T */ public function find($id, $lockMode = null, $lockVersion = null) { return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion); } /** * Finds all entities in the repository. * * @psalm-return list<T> The entities. */ public function findAll() { return $this->findBy([]); } /** * Finds entities by a set of criteria. * * @param int|null $limit * @param int|null $offset * @psalm-param array<string, mixed> $criteria * @psalm-param array<string, string>|null $orderBy * * @return object[] The objects. * @psalm-return list<T> */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->loadAll($criteria, $orderBy, $limit, $offset); } /** * Finds a single entity by a set of criteria. * * @psalm-param array<string, mixed> $criteria * @psalm-param array<string, string>|null $orderBy * * @return object|null The entity instance or NULL if the entity can not be found. * @psalm-return ?T */ public function findOneBy(array $criteria, ?array $orderBy = null) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->load($criteria, null, null, [], null, 1, $orderBy); } /** * Counts entities by a set of criteria. * * @psalm-param array<string, mixed> $criteria * * @return int The cardinality of the objects that match the given criteria. * * @todo Add this method to `ObjectRepository` interface in the next major release */ public function count(array $criteria) { return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria); } /** * Adds support for magic method calls. * * @param string $method * @param mixed[] $arguments * @psalm-param list<mixed> $arguments * * @return mixed The returned value from the resolved method. * * @throws BadMethodCallException If the method called is invalid. */ public function __call($method, $arguments) { if (str_starts_with($method, 'findBy')) { return $this->resolveMagicCall('findBy', substr($method, 6), $arguments); } if (str_starts_with($method, 'findOneBy')) { return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments); } if (str_starts_with($method, 'countBy')) { return $this->resolveMagicCall('count', substr($method, 7), $arguments); } throw new BadMethodCallException(sprintf( 'Undefined method "%s". The method name must start with ' . 'either findBy, findOneBy or countBy!', $method )); } /** * @return string * @psalm-return class-string<T> */ protected function getEntityName() { return $this->_entityName; } /** * {@inheritDoc} */ public function getClassName() { return $this->getEntityName(); } /** @return EntityManagerInterface */ protected function getEntityManager() { return $this->_em; } /** * @return ClassMetadata * @psalm-return ClassMetadata<T> */ protected function getClassMetadata() { return $this->_class; } /** * Select all elements from a selectable that match the expression and * return a new collection containing these elements. * * @return AbstractLazyCollection * @psalm-return AbstractLazyCollection<int, T>&Selectable<int, T> */ public function matching(Criteria $criteria) { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return new LazyCriteriaCollection($persister, $criteria); } /** * Resolves a magic method call to the proper existent method at `EntityRepository`. * * @param string $method The method to call * @param string $by The property name used as condition * @psalm-param list<mixed> $arguments The arguments to pass at method call * * @return mixed * * @throws InvalidMagicMethodCall If the method called is invalid or the * requested field/association does not exist. */ private function resolveMagicCall(string $method, string $by, array $arguments) { if (! $arguments) { throw InvalidMagicMethodCall::onMissingParameter($method . $by); } if (self::$inflector === null) { self::$inflector = InflectorFactory::create()->build(); } $fieldName = lcfirst(self::$inflector->classify($by)); if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) { throw InvalidMagicMethodCall::becauseFieldNotFoundIn( $this->_entityName, $fieldName, $method . $by ); } return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1)); } } orm/lib/Doctrine/ORM/Events.php 0000644 00000010104 15120025736 0012265 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; /** * Container for all ORM events. * * This class cannot be instantiated. */ final class Events { /** * Private constructor. This class is not meant to be instantiated. */ private function __construct() { } /** * The preRemove event occurs for a given entity before the respective * EntityManager remove operation for that entity is executed. * * This is an entity lifecycle event. */ public const preRemove = 'preRemove'; /** * The postRemove event occurs for an entity after the entity has * been deleted. It will be invoked after the database delete operations. * * This is an entity lifecycle event. */ public const postRemove = 'postRemove'; /** * The prePersist event occurs for a given entity before the respective * EntityManager persist operation for that entity is executed. * * This is an entity lifecycle event. */ public const prePersist = 'prePersist'; /** * The postPersist event occurs for an entity after the entity has * been made persistent. It will be invoked after the database insert operations. * Generated primary key values are available in the postPersist event. * * This is an entity lifecycle event. */ public const postPersist = 'postPersist'; /** * The preUpdate event occurs before the database update operations to * entity data. * * This is an entity lifecycle event. */ public const preUpdate = 'preUpdate'; /** * The postUpdate event occurs after the database update operations to * entity data. * * This is an entity lifecycle event. */ public const postUpdate = 'postUpdate'; /** * The postLoad event occurs for an entity after the entity has been loaded * into the current EntityManager from the database or after the refresh operation * has been applied to it. * * Note that the postLoad event occurs for an entity before any associations have been * initialized. Therefore, it is not safe to access associations in a postLoad callback * or event handler. * * This is an entity lifecycle event. */ public const postLoad = 'postLoad'; /** * The loadClassMetadata event occurs after the mapping metadata for a class * has been loaded from a mapping source (attributes/xml/yaml). */ public const loadClassMetadata = 'loadClassMetadata'; /** * The onClassMetadataNotFound event occurs whenever loading metadata for a class * failed. */ public const onClassMetadataNotFound = 'onClassMetadataNotFound'; /** * The preFlush event occurs when the EntityManager#flush() operation is invoked, * but before any changes to managed entities have been calculated. This event is * always raised right after EntityManager#flush() call. */ public const preFlush = 'preFlush'; /** * The onFlush event occurs when the EntityManager#flush() operation is invoked, * after any changes to managed entities have been determined but before any * actual database operations are executed. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the onFlush event is not raised. */ public const onFlush = 'onFlush'; /** * The postFlush event occurs when the EntityManager#flush() operation is invoked and * after all actual database operations are executed successfully. The event is only raised if there is * actually something to do for the underlying UnitOfWork. If nothing needs to be done, * the postFlush event is not raised. The event won't be raised if an error occurs during the * flush operation. */ public const postFlush = 'postFlush'; /** * The onClear event occurs when the EntityManager#clear() operation is invoked, * after all references to entities have been removed from the unit of work. */ public const onClear = 'onClear'; } orm/lib/Doctrine/ORM/LazyCriteriaCollection.php 0000644 00000005464 15120025736 0015454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Collections\AbstractLazyCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Selectable; use Doctrine\ORM\Persisters\Entity\EntityPersister; use ReturnTypeWillChange; use function assert; /** * A lazy collection that allows a fast count when using criteria object * Once count gets executed once without collection being initialized, result * is cached and returned on subsequent calls until collection gets loaded, * then returning the number of loaded results. * * @template TKey of array-key * @template TValue of object * @extends AbstractLazyCollection<TKey, TValue> * @implements Selectable<TKey, TValue> */ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectable { /** @var EntityPersister */ protected $entityPersister; /** @var Criteria */ protected $criteria; /** @var int|null */ private $count; public function __construct(EntityPersister $entityPersister, Criteria $criteria) { $this->entityPersister = $entityPersister; $this->criteria = $criteria; } /** * Do an efficient count on the collection * * @return int */ #[ReturnTypeWillChange] public function count() { if ($this->isInitialized()) { return $this->collection->count(); } // Return cached result in case count query was already executed if ($this->count !== null) { return $this->count; } return $this->count = $this->entityPersister->count($this->criteria); } /** * check if collection is empty without loading it * * @return bool TRUE if the collection is empty, FALSE otherwise. */ public function isEmpty() { if ($this->isInitialized()) { return $this->collection->isEmpty(); } return ! $this->count(); } /** * {@inheritDoc} * * Do an optimized search of an element * * @template TMaybeContained */ public function contains($element) { if ($this->isInitialized()) { return $this->collection->contains($element); } return $this->entityPersister->exists($element, $this->criteria); } /** * {@inheritDoc} */ public function matching(Criteria $criteria) { $this->initialize(); assert($this->collection instanceof Selectable); return $this->collection->matching($criteria); } /** * {@inheritDoc} */ protected function doInitialize() { $elements = $this->entityPersister->loadCriteria($this->criteria); $this->collection = new ArrayCollection($elements); } } orm/lib/Doctrine/ORM/NativeQuery.php 0000644 00000003056 15120025736 0013305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use function array_values; use function is_int; use function key; use function ksort; /** * Represents a native SQL query. */ final class NativeQuery extends AbstractQuery { /** @var string */ private $sql; /** * Sets the SQL of the query. * * @param string $sql * * @return $this */ public function setSQL($sql): self { $this->sql = $sql; return $this; } /** * Gets the SQL query. */ public function getSQL(): string { return $this->sql; } /** * {@inheritDoc} */ protected function _doExecute() { $parameters = []; $types = []; foreach ($this->getParameters() as $parameter) { $name = $parameter->getName(); $value = $this->processParameterValue($parameter->getValue()); $type = $parameter->getValue() === $value ? $parameter->getType() : Query\ParameterTypeInferer::inferType($value); $parameters[$name] = $value; $types[$name] = $type; } if ($parameters && is_int(key($parameters))) { ksort($parameters); ksort($types); $parameters = array_values($parameters); $types = array_values($types); } return $this->_em->getConnection()->executeQuery( $this->sql, $parameters, $types, $this->_queryCacheProfile ); } } orm/lib/Doctrine/ORM/NoResultException.php 0000644 00000000544 15120025736 0014462 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly does not return any results. */ class NoResultException extends UnexpectedResultException { public function __construct() { parent::__construct('No result was found for query although at least one row was expected.'); } } orm/lib/Doctrine/ORM/NonUniqueResultException.php 0000644 00000000724 15120025736 0016027 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; /** * Exception thrown when an ORM query unexpectedly returns more than one result. */ class NonUniqueResultException extends UnexpectedResultException { public const DEFAULT_MESSAGE = 'More than one result was found for query although one row or none was expected.'; public function __construct(?string $message = null) { parent::__construct($message ?? self::DEFAULT_MESSAGE); } } orm/lib/Doctrine/ORM/ORMException.php 0000644 00000021272 15120025736 0013345 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Cache\Cache as CacheDriver; use Doctrine\Persistence\ObjectRepository; use Exception; use function get_debug_type; use function implode; use function sprintf; /** * Base exception class for all ORM exceptions. * * @deprecated Use Doctrine\ORM\Exception\ORMException for catch and instanceof */ class ORMException extends Exception { /** * @deprecated Use Doctrine\ORM\Exception\MissingMappingDriverImplementation * * @return ORMException */ public static function missingMappingDriverImpl() { return new self("It's a requirement to specify a Metadata Driver and pass it " . 'to Doctrine\\ORM\\Configuration::setMetadataDriverImpl().'); } /** * @deprecated Use Doctrine\ORM\Exception\NamedQueryNotFound * * @param string $queryName * * @return ORMException */ public static function namedQueryNotFound($queryName) { return new self('Could not find a named query by the name "' . $queryName . '"'); } /** * @deprecated Use Doctrine\ORM\Exception\NamedQueryNotFound * * @param string $nativeQueryName * * @return ORMException */ public static function namedNativeQueryNotFound($nativeQueryName) { return new self('Could not find a named native query by the name "' . $nativeQueryName . '"'); } /** * @deprecated Use Doctrine\ORM\Persisters\Exception\UnrecognizedField * * @param string $field * * @return ORMException */ public static function unrecognizedField($field) { return new self(sprintf('Unrecognized field: %s', $field)); } /** * @deprecated Use Doctrine\ORM\Exception\UnexpectedAssociationValue * * @param string $class * @param string $association * @param string $given * @param string $expected * * @return ORMException */ public static function unexpectedAssociationValue($class, $association, $given, $expected) { return new self(sprintf('Found entity of type %s on association %s#%s, but expecting %s', $given, $class, $association, $expected)); } /** * @deprecated Use Doctrine\ORM\Persisters\Exception\InvalidOrientation * * @param string $className * @param string $field * * @return ORMException */ public static function invalidOrientation($className, $field) { return new self('Invalid order by orientation specified for ' . $className . '#' . $field); } /** * @deprecated Use Doctrine\ORM\Exception\EntityManagerClosed * * @return ORMException */ public static function entityManagerClosed() { return new self('The EntityManager is closed.'); } /** * @deprecated Use Doctrine\ORM\Exception\InvalidHydrationMode * * @param string $mode * * @return ORMException */ public static function invalidHydrationMode($mode) { return new self(sprintf("'%s' is an invalid hydration mode.", $mode)); } /** * @deprecated Use Doctrine\ORM\Exception\MismatchedEventManager * * @return ORMException */ public static function mismatchedEventManager() { return new self('Cannot use different EventManager instances for EntityManager and Connection.'); } /** * @deprecated Use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall::onMissingParameter() * * @param string $methodName * * @return ORMException */ public static function findByRequiresParameter($methodName) { return new self("You need to pass a parameter to '" . $methodName . "'"); } /** * @deprecated Doctrine\ORM\Repository\Exception\InvalidFindByCall * * @param string $entityName * @param string $fieldName * @param string $method * * @return ORMException */ public static function invalidMagicCall($entityName, $fieldName, $method) { return new self( "Entity '" . $entityName . "' has no field '" . $fieldName . "'. " . "You can therefore not call '" . $method . "' on the entities' repository" ); } /** * @deprecated Use Doctrine\ORM\Repository\Exception\InvalidFindByCall::fromInverseSideUsage() * * @param string $entityName * @param string $associationFieldName * * @return ORMException */ public static function invalidFindByInverseAssociation($entityName, $associationFieldName) { return new self( "You cannot search for the association field '" . $entityName . '#' . $associationFieldName . "', " . 'because it is the inverse side of an association. Find methods only work on owning side associations.' ); } /** * @deprecated Use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver * * @return ORMException */ public static function invalidResultCacheDriver() { return new self('Invalid result cache driver; it must implement Doctrine\\Common\\Cache\\Cache.'); } /** * @deprecated Doctrine\ORM\Tools\Exception\NotSupported * * @return ORMException */ public static function notSupported() { return new self('This behaviour is (currently) not supported by Doctrine 2'); } /** * @deprecated Use Doctrine\ORM\Cache\Exception\QueryCacheNotConfigured * * @return ORMException */ public static function queryCacheNotConfigured() { return new self('Query Cache is not configured.'); } /** * @deprecated Use Doctrine\ORM\Cache\Exception\MetadataCacheNotConfigured * * @return ORMException */ public static function metadataCacheNotConfigured() { return new self('Class Metadata Cache is not configured.'); } /** * @deprecated Use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache * * @return ORMException */ public static function queryCacheUsesNonPersistentCache(CacheDriver $cache) { return new self('Query Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.'); } /** * @deprecated Use Doctrine\ORM\Cache\Exception\MetadataCacheUsesNonPersistentCache * * @return ORMException */ public static function metadataCacheUsesNonPersistentCache(CacheDriver $cache) { return new self('Metadata Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.'); } /** * @deprecated Use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating * * @return ORMException */ public static function proxyClassesAlwaysRegenerating() { return new self('Proxy Classes are always regenerating.'); } /** * @deprecated Use Doctrine\ORM\Exception\UnknownEntityNamespace * * @param string $entityNamespaceAlias * * @return ORMException */ public static function unknownEntityNamespace($entityNamespaceAlias) { return new self( sprintf("Unknown Entity namespace alias '%s'.", $entityNamespaceAlias) ); } /** * @deprecated Use Doctrine\ORM\Exception\InvalidEntityRepository * * @param string $className * * @return ORMException */ public static function invalidEntityRepository($className) { return new self(sprintf( "Invalid repository class '%s'. It must be a %s.", $className, ObjectRepository::class )); } /** * @deprecated Use Doctrine\ORM\Exception\MissingIdentifierField * * @param string $className * @param string $fieldName * * @return ORMException */ public static function missingIdentifierField($className, $fieldName) { return new self(sprintf('The identifier %s is missing for a query of %s', $fieldName, $className)); } /** * @deprecated Use Doctrine\ORM\Exception\UnrecognizedIdentifierFields * * @param string $className * @param string[] $fieldNames * * @return ORMException */ public static function unrecognizedIdentifierFields($className, $fieldNames) { return new self( "Unrecognized identifier fields: '" . implode("', '", $fieldNames) . "' " . "are not present on class '" . $className . "'." ); } /** * @deprecated Use Doctrine\ORM\Persisters\Exception\CantUseInOperatorOnCompositeKeys * * @return ORMException */ public static function cantUseInOperatorOnCompositeKeys() { return new self("Can't use IN operator on entities that have composite keys."); } } orm/lib/Doctrine/ORM/ORMInvalidArgumentException.php 0000644 00000022610 15120025736 0016354 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\ClassMetadata; use InvalidArgumentException; use function array_map; use function count; use function func_get_arg; use function func_num_args; use function get_debug_type; use function gettype; use function implode; use function method_exists; use function reset; use function spl_object_id; use function sprintf; /** * Contains exception messages for all invalid lifecycle state exceptions inside UnitOfWork * * @psalm-import-type AssociationMapping from ClassMetadata */ class ORMInvalidArgumentException extends InvalidArgumentException { /** * @param object $entity * * @return ORMInvalidArgumentException */ public static function scheduleInsertForManagedEntity($entity) { return new self('A managed+dirty entity ' . self::objToStr($entity) . ' can not be scheduled for insertion.'); } /** * @param object $entity * * @return ORMInvalidArgumentException */ public static function scheduleInsertForRemovedEntity($entity) { return new self('Removed entity ' . self::objToStr($entity) . ' can not be scheduled for insertion.'); } /** * @param object $entity * * @return ORMInvalidArgumentException */ public static function scheduleInsertTwice($entity) { return new self('Entity ' . self::objToStr($entity) . ' can not be scheduled for insertion twice.'); } /** * @param string $className * @param object $entity * * @return ORMInvalidArgumentException */ public static function entityWithoutIdentity($className, $entity) { return new self( "The given entity of type '" . $className . "' (" . self::objToStr($entity) . ') has no identity/no ' . 'id values set. It cannot be added to the identity map.' ); } /** * @param object $entity * * @return ORMInvalidArgumentException */ public static function readOnlyRequiresManagedEntity($entity) { return new self('Only managed entities can be marked or checked as read only. But ' . self::objToStr($entity) . ' is not'); } /** * @psalm-param non-empty-list<array{AssociationMapping, object}> $newEntitiesWithAssociations non-empty an array * of [array $associationMapping, object $entity] pairs * * @return ORMInvalidArgumentException */ public static function newEntitiesFoundThroughRelationships($newEntitiesWithAssociations) { $errorMessages = array_map( static function (array $newEntityWithAssociation): string { [$associationMapping, $entity] = $newEntityWithAssociation; return self::newEntityFoundThroughRelationshipMessage($associationMapping, $entity); }, $newEntitiesWithAssociations ); if (count($errorMessages) === 1) { return new self(reset($errorMessages)); } return new self( 'Multiple non-persisted new entities were found through the given association graph:' . "\n\n * " . implode("\n * ", $errorMessages) ); } /** * @param object $entry * @psalm-param AssociationMapping $associationMapping * * @return ORMInvalidArgumentException */ public static function newEntityFoundThroughRelationship(array $associationMapping, $entry) { return new self(self::newEntityFoundThroughRelationshipMessage($associationMapping, $entry)); } /** * @param object $entry * @psalm-param AssociationMapping $assoc * * @return ORMInvalidArgumentException */ public static function detachedEntityFoundThroughRelationship(array $assoc, $entry) { return new self('A detached entity of type ' . $assoc['targetEntity'] . ' (' . self::objToStr($entry) . ') ' . " was found through the relationship '" . $assoc['sourceEntity'] . '#' . $assoc['fieldName'] . "' " . 'during cascading a persist operation.'); } /** * @param object $entity * * @return ORMInvalidArgumentException */ public static function entityNotManaged($entity) { return new self('Entity ' . self::objToStr($entity) . ' is not managed. An entity is managed if its fetched ' . 'from the database or registered as new through EntityManager#persist'); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ public static function entityHasNoIdentity($entity, $operation) { return new self('Entity has no identity, therefore ' . $operation . ' cannot be performed. ' . self::objToStr($entity)); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ public static function entityIsRemoved($entity, $operation) { return new self('Entity is removed, therefore ' . $operation . ' cannot be performed. ' . self::objToStr($entity)); } /** * @param object $entity * @param string $operation * * @return ORMInvalidArgumentException */ public static function detachedEntityCannot($entity, $operation) { return new self('Detached entity ' . self::objToStr($entity) . ' cannot be ' . $operation); } /** * @param string $context * @param mixed $given * @param int $parameterIndex * * @return ORMInvalidArgumentException */ public static function invalidObject($context, $given, $parameterIndex = 1) { return new self($context . ' expects parameter ' . $parameterIndex . ' to be an entity object, ' . gettype($given) . ' given.'); } /** @return ORMInvalidArgumentException */ public static function invalidCompositeIdentifier() { return new self('Binding an entity with a composite primary key to a query is not supported. ' . 'You should split the parameter into the explicit fields and bind them separately.'); } /** @return ORMInvalidArgumentException */ public static function invalidIdentifierBindingEntity(/* string $class */) { if (func_num_args() === 0) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9642', 'Omitting the class name in the exception method %s is deprecated.', __METHOD__ ); return new self('Binding entities to query parameters only allowed for entities that have an identifier.'); } return new self(sprintf( <<<'EXCEPTION' Binding entities to query parameters only allowed for entities that have an identifier. Class "%s" does not have an identifier. EXCEPTION , func_get_arg(0) )); } /** * @param AssociationMapping $assoc * @param mixed $actualValue * * @return self */ public static function invalidAssociation(ClassMetadata $targetClass, $assoc, $actualValue) { $expectedType = $targetClass->getName(); return new self(sprintf( 'Expected value of type "%s" for association field "%s#$%s", got "%s" instead.', $expectedType, $assoc['sourceEntity'], $assoc['fieldName'], get_debug_type($actualValue) )); } /** * Used when a given entityName hasn't the good type * * @deprecated This method will be removed in 3.0. * * @param mixed $entityName The given entity (which shouldn't be a string) * * @return self */ public static function invalidEntityName($entityName) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9471', '%s() is deprecated', __METHOD__ ); return new self(sprintf('Entity name must be a string, %s given', get_debug_type($entityName))); } /** * Helper method to show an object as string. * * @param object $obj */ private static function objToStr($obj): string { return method_exists($obj, '__toString') ? (string) $obj : get_debug_type($obj) . '@' . spl_object_id($obj); } /** * @param object $entity * @psalm-param AssociationMapping $associationMapping */ private static function newEntityFoundThroughRelationshipMessage(array $associationMapping, $entity): string { return 'A new entity was found through the relationship \'' . $associationMapping['sourceEntity'] . '#' . $associationMapping['fieldName'] . '\' that was not' . ' configured to cascade persist operations for entity: ' . self::objToStr($entity) . '.' . ' To solve this issue: Either explicitly call EntityManager#persist()' . ' on this unknown entity or configure cascade persist' . ' this association in the mapping for example @ManyToOne(..,cascade={"persist"}).' . (method_exists($entity, '__toString') ? '' : ' If you cannot find out which entity causes the problem implement \'' . $associationMapping['targetEntity'] . '#__toString()\' to get a clue.' ); } } orm/lib/Doctrine/ORM/ORMSetup.php 0000644 00000015415 15120025736 0012511 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; use Doctrine\ORM\Mapping\Driver\YamlDriver; use LogicException; use Psr\Cache\CacheItemPoolInterface; use Redis; use RuntimeException; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\MemcachedAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; use function apcu_enabled; use function class_exists; use function extension_loaded; use function md5; use function sprintf; use function sys_get_temp_dir; final class ORMSetup { /** * Creates a configuration with an annotation metadata driver. * * @deprecated Use another mapping driver. * * @param string[] $paths */ public static function createAnnotationMetadataConfiguration( array $paths, bool $isDevMode = false, ?string $proxyDir = null, ?CacheItemPoolInterface $cache = null ): Configuration { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/10098', '%s is deprecated and will be removed in Doctrine ORM 3.0', __METHOD__ ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(self::createDefaultAnnotationDriver($paths)); return $config; } /** * Adds a new default annotation driver with a correctly configured annotation reader. * * @deprecated Use another mapping driver. * * @param string[] $paths */ public static function createDefaultAnnotationDriver( array $paths = [], ?CacheItemPoolInterface $cache = null ): AnnotationDriver { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/10098', '%s is deprecated and will be removed in Doctrine ORM 3.0', __METHOD__ ); if (! class_exists(AnnotationReader::class)) { throw new LogicException(sprintf( 'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library' . ' is not installed. Please run "composer require doctrine/annotations" or choose a different' . ' metadata driver.' )); } $reader = new AnnotationReader(); if ($cache === null && class_exists(ArrayAdapter::class)) { $cache = new ArrayAdapter(); } if ($cache !== null) { $reader = new PsrCachedReader($reader, $cache); } return new AnnotationDriver($reader, $paths); } /** * Creates a configuration with an attribute metadata driver. * * @param string[] $paths */ public static function createAttributeMetadataConfiguration( array $paths, bool $isDevMode = false, ?string $proxyDir = null, ?CacheItemPoolInterface $cache = null ): Configuration { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new AttributeDriver($paths)); return $config; } /** * Creates a configuration with an XML metadata driver. * * @param string[] $paths */ public static function createXMLMetadataConfiguration( array $paths, bool $isDevMode = false, ?string $proxyDir = null, ?CacheItemPoolInterface $cache = null, bool $isXsdValidationEnabled = false ): Configuration { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new XmlDriver($paths, XmlDriver::DEFAULT_FILE_EXTENSION, $isXsdValidationEnabled)); return $config; } /** * Creates a configuration with a YAML metadata driver. * * @deprecated YAML metadata mapping is deprecated and will be removed in 3.0 * * @param string[] $paths */ public static function createYAMLMetadataConfiguration( array $paths, bool $isDevMode = false, ?string $proxyDir = null, ?CacheItemPoolInterface $cache = null ): Configuration { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8465', 'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.' ); $config = self::createConfiguration($isDevMode, $proxyDir, $cache); $config->setMetadataDriverImpl(new YamlDriver($paths)); return $config; } /** * Creates a configuration without a metadata driver. */ public static function createConfiguration( bool $isDevMode = false, ?string $proxyDir = null, ?CacheItemPoolInterface $cache = null ): Configuration { $proxyDir = $proxyDir ?: sys_get_temp_dir(); $cache = self::createCacheInstance($isDevMode, $proxyDir, $cache); $config = new Configuration(); $config->setMetadataCache($cache); $config->setQueryCache($cache); $config->setResultCache($cache); $config->setProxyDir($proxyDir); $config->setProxyNamespace('DoctrineProxies'); $config->setAutoGenerateProxyClasses($isDevMode); return $config; } private static function createCacheInstance( bool $isDevMode, string $proxyDir, ?CacheItemPoolInterface $cache ): CacheItemPoolInterface { if ($cache !== null) { return $cache; } if (! class_exists(ArrayAdapter::class)) { throw new RuntimeException( 'The Doctrine setup tool cannot configure caches without symfony/cache.' . ' Please add symfony/cache as explicit dependency or pass your own cache implementation.' ); } if ($isDevMode) { return new ArrayAdapter(); } $namespace = 'dc2_' . md5($proxyDir); if (extension_loaded('apcu') && apcu_enabled()) { return new ApcuAdapter($namespace); } if (MemcachedAdapter::isSupported()) { return new MemcachedAdapter(MemcachedAdapter::createConnection('memcached://127.0.0.1'), $namespace); } if (extension_loaded('redis')) { $redis = new Redis(); $redis->connect('127.0.0.1'); return new RedisAdapter($redis, $namespace); } return new ArrayAdapter(); } private function __construct() { } } orm/lib/Doctrine/ORM/OptimisticLockException.php 0000644 00000004042 15120025736 0015641 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use DateTimeInterface; use Doctrine\ORM\Exception\ORMException; /** * An OptimisticLockException is thrown when a version check on an object * that uses optimistic locking through a version field fails. */ class OptimisticLockException extends ORMException { /** @var object|string|null */ private $entity; /** * @param string $msg * @param object|string|null $entity */ public function __construct($msg, $entity) { parent::__construct($msg); $this->entity = $entity; } /** * Gets the entity that caused the exception. * * @return object|string|null */ public function getEntity() { return $this->entity; } /** * @param object|class-string $entity * * @return OptimisticLockException */ public static function lockFailed($entity) { return new self('The optimistic lock on an entity failed.', $entity); } /** * @param object $entity * @param int|string|DateTimeInterface $expectedLockVersion * @param int|string|DateTimeInterface $actualLockVersion * * @return OptimisticLockException */ public static function lockFailedVersionMismatch($entity, $expectedLockVersion, $actualLockVersion) { $expectedLockVersion = $expectedLockVersion instanceof DateTimeInterface ? $expectedLockVersion->getTimestamp() : $expectedLockVersion; $actualLockVersion = $actualLockVersion instanceof DateTimeInterface ? $actualLockVersion->getTimestamp() : $actualLockVersion; return new self('The optimistic lock failed, version ' . $expectedLockVersion . ' was expected, but is actually ' . $actualLockVersion, $entity); } /** * @param string $entityName * * @return OptimisticLockException */ public static function notVersioned($entityName) { return new self('Cannot obtain optimistic lock on unversioned entity ' . $entityName, null); } } orm/lib/Doctrine/ORM/PersistentCollection.php 0000644 00000052144 15120025736 0015207 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Collections\AbstractLazyCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Selectable; use Doctrine\ORM\Mapping\ClassMetadata; use ReturnTypeWillChange; use RuntimeException; use UnexpectedValueException; use function array_combine; use function array_diff_key; use function array_map; use function array_values; use function array_walk; use function assert; use function get_class; use function is_object; use function spl_object_id; /** * A PersistentCollection represents a collection of elements that have persistent state. * * Collections of entities represent only the associations (links) to those entities. * That means, if the collection is part of a many-many mapping and you remove * entities from the collection, only the links in the relation table are removed (on flush). * Similarly, if you remove entities from a collection that is part of a one-many * mapping this will only result in the nulling out of the foreign keys on flush. * * @psalm-template TKey of array-key * @psalm-template T * @template-extends AbstractLazyCollection<TKey,T> * @template-implements Selectable<TKey,T> * @psalm-import-type AssociationMapping from ClassMetadata */ final class PersistentCollection extends AbstractLazyCollection implements Selectable { /** * A snapshot of the collection at the moment it was fetched from the database. * This is used to create a diff of the collection at commit time. * * @psalm-var array<string|int, mixed> */ private $snapshot = []; /** * The entity that owns this collection. * * @var object|null */ private $owner; /** * The association mapping the collection belongs to. * This is currently either a OneToManyMapping or a ManyToManyMapping. * * @psalm-var AssociationMapping|null */ private $association; /** * The EntityManager that manages the persistence of the collection. * * @var EntityManagerInterface|null */ private $em; /** * The name of the field on the target entities that points to the owner * of the collection. This is only set if the association is bi-directional. * * @var string|null */ private $backRefFieldName; /** * The class descriptor of the collection's entity type. * * @var ClassMetadata|null */ private $typeClass; /** * Whether the collection is dirty and needs to be synchronized with the database * when the UnitOfWork that manages its persistent state commits. * * @var bool */ private $isDirty = false; /** * Creates a new persistent collection. * * @param EntityManagerInterface $em The EntityManager the collection will be associated with. * @param ClassMetadata $class The class descriptor of the entity type of this collection. * @psalm-param Collection<TKey, T>&Selectable<TKey, T> $collection The collection elements. */ public function __construct(EntityManagerInterface $em, $class, Collection $collection) { $this->collection = $collection; $this->em = $em; $this->typeClass = $class; $this->initialized = true; } /** * INTERNAL: * Sets the collection's owning entity together with the AssociationMapping that * describes the association between the owner and the elements of the collection. * * @param object $entity * @psalm-param AssociationMapping $assoc */ public function setOwner($entity, array $assoc): void { $this->owner = $entity; $this->association = $assoc; $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy']; } /** * INTERNAL: * Gets the collection owner. * * @return object|null */ public function getOwner() { return $this->owner; } /** @return Mapping\ClassMetadata */ public function getTypeClass(): Mapping\ClassMetadataInfo { assert($this->typeClass !== null); return $this->typeClass; } private function getUnitOfWork(): UnitOfWork { assert($this->em !== null); return $this->em->getUnitOfWork(); } /** * INTERNAL: * Adds an element to a collection during hydration. This will automatically * complete bidirectional associations in the case of a one-to-many association. * * @param mixed $element The element to add. */ public function hydrateAdd($element): void { $this->unwrap()->add($element); // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) { assert($this->typeClass !== null); // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); $this->getUnitOfWork()->setOriginalEntityProperty( spl_object_id($element), $this->backRefFieldName, $this->owner ); } } /** * INTERNAL: * Sets a keyed element in the collection during hydration. * * @param mixed $key The key to set. * @param mixed $element The element to set. */ public function hydrateSet($key, $element): void { $this->unwrap()->set($key, $element); // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) { assert($this->typeClass !== null); // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner ); } } /** * Initializes the collection by loading its contents from the database * if the collection is not yet initialized. */ public function initialize(): void { if ($this->initialized || ! $this->association) { return; } $this->doInitialize(); $this->initialized = true; } /** * INTERNAL: * Tells this collection to take a snapshot of its current state. */ public function takeSnapshot(): void { $this->snapshot = $this->unwrap()->toArray(); $this->isDirty = false; } /** * INTERNAL: * Returns the last snapshot of the elements in the collection. * * @psalm-return array<string|int, mixed> The last snapshot of the elements. */ public function getSnapshot(): array { return $this->snapshot; } /** * INTERNAL: * getDeleteDiff * * @return mixed[] */ public function getDeleteDiff(): array { $collectionItems = $this->unwrap()->toArray(); return array_values(array_diff_key( array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot), array_combine(array_map('spl_object_id', $collectionItems), $collectionItems) )); } /** * INTERNAL: * getInsertDiff * * @return mixed[] */ public function getInsertDiff(): array { $collectionItems = $this->unwrap()->toArray(); return array_values(array_diff_key( array_combine(array_map('spl_object_id', $collectionItems), $collectionItems), array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot) )); } /** * INTERNAL: Gets the association mapping of the collection. * * @psalm-return AssociationMapping */ public function getMapping(): array { if ($this->association === null) { throw new UnexpectedValueException('The underlying association mapping is null although it should not be'); } return $this->association; } /** * Marks this collection as changed/dirty. */ private function changed(): void { if ($this->isDirty) { return; } $this->isDirty = true; if ( $this->association !== null && $this->getMapping()['isOwningSide'] && $this->getMapping()['type'] === ClassMetadata::MANY_TO_MANY && $this->owner && $this->em !== null && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify() ) { $this->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } } /** * Gets a boolean flag indicating whether this collection is dirty which means * its state needs to be synchronized with the database. * * @return bool TRUE if the collection is dirty, FALSE otherwise. */ public function isDirty(): bool { return $this->isDirty; } /** * Sets a boolean flag, indicating whether this collection is dirty. * * @param bool $dirty Whether the collection should be marked dirty or not. */ public function setDirty($dirty): void { $this->isDirty = $dirty; } /** * Sets the initialized flag of the collection, forcing it into that state. * * @param bool $bool */ public function setInitialized($bool): void { $this->initialized = $bool; } /** * {@inheritDoc} */ public function remove($key) { // TODO: If the keys are persistent as well (not yet implemented) // and the collection is not initialized and orphanRemoval is // not used we can issue a straight SQL delete/update on the // association (table). Without initializing the collection. $removed = parent::remove($key); if (! $removed) { return $removed; } $this->changed(); if ( $this->association !== null && $this->getMapping()['type'] & ClassMetadata::TO_MANY && $this->owner && $this->getMapping()['orphanRemoval'] ) { $this->getUnitOfWork()->scheduleOrphanRemoval($removed); } return $removed; } /** * {@inheritDoc} */ public function removeElement($element): bool { $removed = parent::removeElement($element); if (! $removed) { return $removed; } $this->changed(); if ( $this->association !== null && $this->getMapping()['type'] & ClassMetadata::TO_MANY && $this->owner && $this->getMapping()['orphanRemoval'] ) { $this->getUnitOfWork()->scheduleOrphanRemoval($element); } return $removed; } /** * {@inheritDoc} */ public function containsKey($key): bool { if ( ! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY && isset($this->getMapping()['indexBy']) ) { $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); return $this->unwrap()->containsKey($key) || $persister->containsKey($this, $key); } return parent::containsKey($key); } /** * {@inheritDoc} * * @template TMaybeContained */ public function contains($element): bool { if (! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) { $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); return $this->unwrap()->contains($element) || $persister->contains($this, $element); } return parent::contains($element); } /** * {@inheritDoc} */ public function get($key) { if ( ! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY && isset($this->getMapping()['indexBy']) ) { assert($this->em !== null); assert($this->typeClass !== null); if (! $this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->getMapping()['indexBy'])) { return $this->em->find($this->typeClass->name, $key); } return $this->getUnitOfWork()->getCollectionPersister($this->getMapping())->get($this, $key); } return parent::get($key); } public function count(): int { if (! $this->initialized && $this->association !== null && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) { $persister = $this->getUnitOfWork()->getCollectionPersister($this->association); return $persister->count($this) + ($this->isDirty ? $this->unwrap()->count() : 0); } return parent::count(); } /** * {@inheritDoc} */ public function set($key, $value): void { parent::set($key, $value); $this->changed(); if (is_object($value) && $this->em) { $this->getUnitOfWork()->cancelOrphanRemoval($value); } } /** * {@inheritDoc} */ public function add($value): bool { $this->unwrap()->add($value); $this->changed(); if (is_object($value) && $this->em) { $this->getUnitOfWork()->cancelOrphanRemoval($value); } return true; } /* ArrayAccess implementation */ /** * {@inheritDoc} */ public function offsetExists($offset): bool { return $this->containsKey($offset); } /** * {@inheritDoc} */ #[ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); } /** * {@inheritDoc} */ public function offsetSet($offset, $value): void { if (! isset($offset)) { $this->add($value); return; } $this->set($offset, $value); } /** * {@inheritDoc} * * @return object|null */ #[ReturnTypeWillChange] public function offsetUnset($offset) { return $this->remove($offset); } public function isEmpty(): bool { return $this->unwrap()->isEmpty() && $this->count() === 0; } public function clear(): void { if ($this->initialized && $this->isEmpty()) { $this->unwrap()->clear(); return; } $uow = $this->getUnitOfWork(); $association = $this->getMapping(); if ( $association['type'] & ClassMetadata::TO_MANY && $association['orphanRemoval'] && $this->owner ) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); foreach ($this->unwrap() as $element) { $uow->scheduleOrphanRemoval($element); } } $this->unwrap()->clear(); $this->initialized = true; // direct call, {@link initialize()} is too expensive if ($association['isOwningSide'] && $this->owner) { $this->changed(); $uow->scheduleCollectionDeletion($this); $this->takeSnapshot(); } } /** * Called by PHP when this collection is serialized. Ensures that only the * elements are properly serialized. * * Internal note: Tried to implement Serializable first but that did not work well * with circular references. This solution seems simpler and works well. * * @return string[] * @psalm-return array{0: string, 1: string} */ public function __sleep(): array { return ['collection', 'initialized']; } /** * Extracts a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset * @param int|null $length * * @return mixed[] * @psalm-return array<TKey,T> */ public function slice($offset, $length = null): array { if (! $this->initialized && ! $this->isDirty && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) { $persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping()); return $persister->slice($this, $offset, $length); } return parent::slice($offset, $length); } /** * Cleans up internal state of cloned persistent collection. * * The following problems have to be prevented: * 1. Added entities are added to old PC * 2. New collection is not dirty, if reused on other entity nothing * changes. * 3. Snapshot leads to invalid diffs being generated. * 4. Lazy loading grabs entities from old owner object. * 5. New collection is connected to old owner and leads to duplicate keys. */ public function __clone() { if (is_object($this->collection)) { $this->collection = clone $this->collection; } $this->initialize(); $this->owner = null; $this->snapshot = []; $this->changed(); } /** * Selects all elements from a selectable that match the expression and * return a new collection containing these elements. * * @psalm-return Collection<TKey, T> * * @throws RuntimeException */ public function matching(Criteria $criteria): Collection { if ($this->isDirty) { $this->initialize(); } if ($this->initialized) { return $this->unwrap()->matching($criteria); } $association = $this->getMapping(); if ($association['type'] === ClassMetadata::MANY_TO_MANY) { $persister = $this->getUnitOfWork()->getCollectionPersister($association); return new ArrayCollection($persister->loadCriteria($this, $criteria)); } $builder = Criteria::expr(); $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner); $expression = $criteria->getWhereExpression(); $expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression; $criteria = clone $criteria; $criteria->where($expression); $criteria->orderBy($criteria->getOrderings() ?: $association['orderBy'] ?? []); $persister = $this->getUnitOfWork()->getEntityPersister($association['targetEntity']); return $association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY ? new LazyCriteriaCollection($persister, $criteria) : new ArrayCollection($persister->loadCriteria($criteria)); } /** * Retrieves the wrapped Collection instance. * * @return Collection<TKey, T>&Selectable<TKey, T> */ public function unwrap(): Collection { assert($this->collection instanceof Collection); assert($this->collection instanceof Selectable); return $this->collection; } protected function doInitialize(): void { // Has NEW objects added through add(). Remember them. $newlyAddedDirtyObjects = []; if ($this->isDirty) { $newlyAddedDirtyObjects = $this->unwrap()->toArray(); } $this->unwrap()->clear(); $this->getUnitOfWork()->loadCollection($this); $this->takeSnapshot(); if ($newlyAddedDirtyObjects) { $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects); } } /** * @param object[] $newObjects * * Note: the only reason why this entire looping/complexity is performed via `spl_object_id` * is because we want to prevent using `array_udiff()`, which is likely to cause very * high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in * core, which is faster than using a callback for comparisons */ private function restoreNewObjectsInDirtyCollection(array $newObjects): void { $loadedObjects = $this->unwrap()->toArray(); $newObjectsByOid = array_combine(array_map('spl_object_id', $newObjects), $newObjects); $loadedObjectsByOid = array_combine(array_map('spl_object_id', $loadedObjects), $loadedObjects); $newObjectsThatWereNotLoaded = array_diff_key($newObjectsByOid, $loadedObjectsByOid); if ($newObjectsThatWereNotLoaded) { // Reattach NEW objects added through add(), if any. array_walk($newObjectsThatWereNotLoaded, [$this->unwrap(), 'add']); $this->isDirty = true; } } } orm/lib/Doctrine/ORM/PessimisticLockException.php 0000644 00000000465 15120025736 0016016 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\ORM\Exception\ORMException; class PessimisticLockException extends ORMException { /** @return PessimisticLockException */ public static function lockFailed() { return new self('The pessimistic lock failed.'); } } orm/lib/Doctrine/ORM/Query.php 0000644 00000056174 15120025736 0012147 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Internal\Hydration\IterableResult; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\AST\DeleteStatement; use Doctrine\ORM\Query\AST\SelectStatement; use Doctrine\ORM\Query\AST\UpdateStatement; use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\ParameterTypeInferer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; use Psr\Cache\CacheItemPoolInterface; use function array_keys; use function array_values; use function assert; use function count; use function get_debug_type; use function in_array; use function is_int; use function ksort; use function md5; use function method_exists; use function reset; use function serialize; use function sha1; use function stripos; /** * A Query object represents a DQL query. */ final class Query extends AbstractQuery { /** * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. */ public const STATE_CLEAN = 1; /** * A query object is in state DIRTY when it has DQL parts that have not yet been * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart * is called. */ public const STATE_DIRTY = 2; /* Query HINTS */ /** * The refresh hint turns any query into a refresh query with the result that * any local changes in entities are overridden with the fetched values. */ public const HINT_REFRESH = 'doctrine.refresh'; public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled'; public const HINT_CACHE_EVICT = 'doctrine.cache.evict'; /** * Internal hint: is set to the proxy entity that is currently triggered for loading */ public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity'; /** * The forcePartialLoad query hint forces a particular query to return * partial objects. * * @todo Rename: HINT_OPTIMIZE */ public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad'; /** * The includeMetaColumns query hint causes meta columns like foreign keys and * discriminator columns to be selected and returned as part of the query result. * * This hint does only apply to non-object queries. */ public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; /** * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and * are iterated and executed after the DQL has been parsed into an AST. */ public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers'; /** * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker * and is used for generating the target SQL from any DQL AST tree. */ public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker'; /** * Marks queries as creating only read only objects. * * If the object retrieved from the query is already in the identity map * then it does not get marked as read only if it wasn't already. */ public const HINT_READ_ONLY = 'doctrine.readOnly'; public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; public const HINT_LOCK_MODE = 'doctrine.lockMode'; /** * The current state of this query. * * @var int * @psalm-var self::STATE_* */ private $state = self::STATE_DIRTY; /** * A snapshot of the parameter types the query was parsed with. * * @var array<string,Type> */ private $parsedTypes = []; /** * Cached DQL query. * * @var string|null */ private $dql = null; /** * The parser result that holds DQL => SQL information. * * @var ParserResult */ private $parserResult; /** * The first result to return (the "offset"). * * @var int */ private $firstResult = 0; /** * The maximum number of results to return (the "limit"). * * @var int|null */ private $maxResults = null; /** * The cache driver used for caching queries. * * @var CacheItemPoolInterface|null */ private $queryCache; /** * Whether or not expire the query cache. * * @var bool */ private $expireQueryCache = false; /** * The query cache lifetime. * * @var int|null */ private $queryCacheTTL; /** * Whether to use a query cache, if available. Defaults to TRUE. * * @var bool */ private $useQueryCache = true; /** * Gets the SQL query/queries that correspond to this DQL query. * * @return list<string>|string The built sql query or an array of all sql queries. */ public function getSQL() { return $this->parse()->getSqlExecutor()->getSqlStatements(); } /** * Returns the corresponding AST for this DQL query. * * @return SelectStatement|UpdateStatement|DeleteStatement */ public function getAST() { $parser = new Parser($this); return $parser->getAST(); } /** * {@inheritDoc} * * @return ResultSetMapping */ protected function getResultSetMapping() { // parse query or load from cache if ($this->_resultSetMapping === null) { $this->_resultSetMapping = $this->parse()->getResultSetMapping(); } return $this->_resultSetMapping; } /** * Parses the DQL query, if necessary, and stores the parser result. * * Note: Populates $this->_parserResult as a side-effect. */ private function parse(): ParserResult { $types = []; foreach ($this->parameters as $parameter) { /** @var Query\Parameter $parameter */ $types[$parameter->getName()] = $parameter->getType(); } // Return previous parser result if the query and the filter collection are both clean if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->_em->isFiltersStateClean()) { return $this->parserResult; } $this->state = self::STATE_CLEAN; $this->parsedTypes = $types; $queryCache = $this->queryCache ?? $this->_em->getConfiguration()->getQueryCache(); // Check query cache. if (! ($this->useQueryCache && $queryCache)) { $parser = new Parser($this); $this->parserResult = $parser->parse(); return $this->parserResult; } $cacheItem = $queryCache->getItem($this->getQueryCacheId()); if (! $this->expireQueryCache && $cacheItem->isHit()) { $cached = $cacheItem->get(); if ($cached instanceof ParserResult) { // Cache hit. $this->parserResult = $cached; return $this->parserResult; } } // Cache miss. $parser = new Parser($this); $this->parserResult = $parser->parse(); $queryCache->save($cacheItem->set($this->parserResult)->expiresAfter($this->queryCacheTTL)); return $this->parserResult; } /** * {@inheritDoc} */ protected function _doExecute() { $executor = $this->parse()->getSqlExecutor(); if ($this->_queryCacheProfile) { $executor->setQueryCacheProfile($this->_queryCacheProfile); } else { $executor->removeQueryCacheProfile(); } if ($this->_resultSetMapping === null) { $this->_resultSetMapping = $this->parserResult->getResultSetMapping(); } // Prepare parameters $paramMappings = $this->parserResult->getParameterMappings(); $paramCount = count($this->parameters); $mappingCount = count($paramMappings); if ($paramCount > $mappingCount) { throw QueryException::tooManyParameters($mappingCount, $paramCount); } if ($paramCount < $mappingCount) { throw QueryException::tooFewParameters($mappingCount, $paramCount); } // evict all cache for the entity region if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) { $this->evictEntityCacheRegion(); } [$sqlParams, $types] = $this->processParameterMappings($paramMappings); $this->evictResultSetCache( $executor, $sqlParams, $types, $this->_em->getConnection()->getParams() ); return $executor->execute($this->_em->getConnection(), $sqlParams, $types); } /** * @param array<string,mixed> $sqlParams * @param array<string,Type> $types * @param array<string,mixed> $connectionParams */ private function evictResultSetCache( AbstractSqlExecutor $executor, array $sqlParams, array $types, array $connectionParams ): void { if ($this->_queryCacheProfile === null || ! $this->getExpireResultCache()) { return; } $cache = method_exists(QueryCacheProfile::class, 'getResultCache') ? $this->_queryCacheProfile->getResultCache() : $this->_queryCacheProfile->getResultCacheDriver(); assert($cache !== null); $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array foreach ($statements as $statement) { $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams); $cache instanceof CacheItemPoolInterface ? $cache->deleteItem(reset($cacheKeys)) : $cache->delete(reset($cacheKeys)); } } /** * Evict entity cache region */ private function evictEntityCacheRegion(): void { $AST = $this->getAST(); if ($AST instanceof SelectStatement) { throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.'); } $className = $AST instanceof DeleteStatement ? $AST->deleteClause->abstractSchemaName : $AST->updateClause->abstractSchemaName; $this->_em->getCache()->evictEntityRegion($className); } /** * Processes query parameter mappings. * * @param array<list<int>> $paramMappings * * @return mixed[][] * @psalm-return array{0: list<mixed>, 1: array} * * @throws Query\QueryException */ private function processParameterMappings(array $paramMappings): array { $sqlParams = []; $types = []; foreach ($this->parameters as $parameter) { $key = $parameter->getName(); if (! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } [$value, $type] = $this->resolveParameterValue($parameter); foreach ($paramMappings[$key] as $position) { $types[$position] = $type; } $sqlPositions = $paramMappings[$key]; // optimized multi value sql positions away for now, // they are not allowed in DQL anyways. $value = [$value]; $countValue = count($value); for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { $sqlParams[$sqlPositions[$i]] = $value[$i % $countValue]; } } if (count($sqlParams) !== count($types)) { throw QueryException::parameterTypeMismatch(); } if ($sqlParams) { ksort($sqlParams); $sqlParams = array_values($sqlParams); ksort($types); $types = array_values($types); } return [$sqlParams, $types]; } /** * @return mixed[] tuple of (value, type) * @psalm-return array{0: mixed, 1: mixed} */ private function resolveParameterValue(Parameter $parameter): array { if ($parameter->typeWasSpecified()) { return [$parameter->getValue(), $parameter->getType()]; } $key = $parameter->getName(); $originalValue = $parameter->getValue(); $value = $originalValue; $rsm = $this->getResultSetMapping(); if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) { $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); } if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) { $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em)); } $processedValue = $this->processParameterValue($value); return [ $processedValue, $originalValue === $processedValue ? $parameter->getType() : ParameterTypeInferer::inferType($processedValue), ]; } /** * Defines a cache driver to be used for caching queries. * * @deprecated Call {@see setQueryCache()} instead. * * @param Cache|null $queryCache Cache driver. * * @return $this */ public function setQueryCacheDriver($queryCache): self { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9004', '%s is deprecated and will be removed in Doctrine 3.0. Use setQueryCache() instead.', __METHOD__ ); $this->queryCache = $queryCache ? CacheAdapter::wrap($queryCache) : null; return $this; } /** * Defines a cache driver to be used for caching queries. * * @return $this */ public function setQueryCache(?CacheItemPoolInterface $queryCache): self { $this->queryCache = $queryCache; return $this; } /** * Defines whether the query should make use of a query cache, if available. * * @param bool $bool * * @return $this */ public function useQueryCache($bool): self { $this->useQueryCache = $bool; return $this; } /** * Returns the cache driver used for query caching. * * @deprecated * * @return Cache|null The cache driver used for query caching or NULL, if * this Query does not use query caching. */ public function getQueryCacheDriver(): ?Cache { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9004', '%s is deprecated and will be removed in Doctrine 3.0 without replacement.', __METHOD__ ); $queryCache = $this->queryCache ?? $this->_em->getConfiguration()->getQueryCache(); return $queryCache ? DoctrineProvider::wrap($queryCache) : null; } /** * Defines how long the query cache will be active before expire. * * @param int|null $timeToLive How long the cache entry is valid. * * @return $this */ public function setQueryCacheLifetime($timeToLive): self { if ($timeToLive !== null) { $timeToLive = (int) $timeToLive; } $this->queryCacheTTL = $timeToLive; return $this; } /** * Retrieves the lifetime of resultset cache. */ public function getQueryCacheLifetime(): ?int { return $this->queryCacheTTL; } /** * Defines if the query cache is active or not. * * @param bool $expire Whether or not to force query cache expiration. * * @return $this */ public function expireQueryCache($expire = true): self { $this->expireQueryCache = $expire; return $this; } /** * Retrieves if the query cache is active or not. */ public function getExpireQueryCache(): bool { return $this->expireQueryCache; } public function free(): void { parent::free(); $this->dql = null; $this->state = self::STATE_CLEAN; } /** * Sets a DQL query string. * * @param string|null $dqlQuery DQL Query. */ public function setDQL($dqlQuery): self { if ($dqlQuery === null) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9784', 'Calling %s with null is deprecated and will result in a TypeError in Doctrine 3.0', __METHOD__ ); return $this; } $this->dql = $dqlQuery; $this->state = self::STATE_DIRTY; return $this; } /** * Returns the DQL query that is represented by this query object. */ public function getDQL(): ?string { return $this->dql; } /** * Returns the state of this query object * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. * * @see AbstractQuery::STATE_CLEAN * @see AbstractQuery::STATE_DIRTY * * @return int The query state. * @psalm-return self::STATE_* The query state. */ public function getState(): int { return $this->state; } /** * Method to check if an arbitrary piece of DQL exists * * @param string $dql Arbitrary piece of DQL to check for. */ public function contains($dql): bool { return stripos($this->getDQL(), $dql) !== false; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param int|null $firstResult The first result to return. * * @return $this */ public function setFirstResult($firstResult): self { if (! is_int($firstResult)) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/pull/9809', 'Calling %s with %s is deprecated and will result in a TypeError in Doctrine 3.0. Pass an integer.', __METHOD__, get_debug_type($firstResult) ); $firstResult = (int) $firstResult; } $this->firstResult = $firstResult; $this->state = self::STATE_DIRTY; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns 0 if {@link setFirstResult} was not applied to this query. * * @return int The position of the first result. */ public function getFirstResult(): int { return $this->firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param int|null $maxResults * * @return $this */ public function setMaxResults($maxResults): self { if ($maxResults !== null) { $maxResults = (int) $maxResults; } $this->maxResults = $maxResults; $this->state = self::STATE_DIRTY; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query. * * @return int|null Maximum number of results. */ public function getMaxResults(): ?int { return $this->maxResults; } /** * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * * @deprecated * * @param ArrayCollection|mixed[]|null $parameters The query parameters. * @param string|int $hydrationMode The hydration mode to use. * @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode */ public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT): IterableResult { $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::iterate($parameters, $hydrationMode); } /** {@inheritDoc} */ public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT): iterable { $this->setHint(self::HINT_INTERNAL_ITERATION, true); return parent::toIterable($parameters, $hydrationMode); } /** * {@inheritDoc} */ public function setHint($name, $value): self { $this->state = self::STATE_DIRTY; return parent::setHint($name, $value); } /** * {@inheritDoc} */ public function setHydrationMode($hydrationMode): self { $this->state = self::STATE_DIRTY; return parent::setHydrationMode($hydrationMode); } /** * Set the lock mode for this Query. * * @see \Doctrine\DBAL\LockMode * * @param int $lockMode * @psalm-param LockMode::* $lockMode * * @return $this * * @throws TransactionRequiredException */ public function setLockMode($lockMode): self { if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) { if (! $this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } $this->setHint(self::HINT_LOCK_MODE, $lockMode); return $this; } /** * Get the current lock mode for this query. * * @return int|null The current lock mode of this query or NULL if no specific lock mode is set. */ public function getLockMode(): ?int { $lockMode = $this->getHint(self::HINT_LOCK_MODE); if ($lockMode === false) { return null; } return $lockMode; } /** * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. */ protected function getQueryCacheId(): string { ksort($this->_hints); return md5( $this->getDQL() . serialize($this->_hints) . '&platform=' . get_debug_type($this->getEntityManager()->getConnection()->getDatabasePlatform()) . ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') . '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults . '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT' ); } protected function getHash(): string { return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults); } /** * Cleanup Query resource when clone is called. */ public function __clone() { parent::__clone(); $this->state = self::STATE_DIRTY; } } orm/lib/Doctrine/ORM/QueryBuilder.php 0000644 00000127064 15120025736 0013453 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\QueryExpressionVisitor; use InvalidArgumentException; use RuntimeException; use function array_keys; use function array_merge; use function array_unshift; use function assert; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_array; use function is_numeric; use function is_object; use function is_string; use function key; use function reset; use function sprintf; use function str_starts_with; use function strpos; use function strrpos; use function substr; /** * This class is responsible for building DQL query strings via an object oriented * PHP interface. */ class QueryBuilder { /** @deprecated */ public const SELECT = 0; /** @deprecated */ public const DELETE = 1; /** @deprecated */ public const UPDATE = 2; /** @deprecated */ public const STATE_DIRTY = 0; /** @deprecated */ public const STATE_CLEAN = 1; /** * The EntityManager used by this QueryBuilder. * * @var EntityManagerInterface */ private $em; /** * The array of DQL parts collected. * * @psalm-var array<string, mixed> */ private $dqlParts = [ 'distinct' => false, 'select' => [], 'from' => [], 'join' => [], 'set' => [], 'where' => null, 'groupBy' => [], 'having' => null, 'orderBy' => [], ]; /** * The type of query this is. Can be select, update or delete. * * @var int * @psalm-var self::SELECT|self::DELETE|self::UPDATE */ private $type = self::SELECT; /** * The state of the query object. Can be dirty or clean. * * @var int * @psalm-var self::STATE_* */ private $state = self::STATE_CLEAN; /** * The complete DQL string for this query. * * @var string|null */ private $dql; /** * The query parameters. * * @var ArrayCollection * @psalm-var ArrayCollection<int, Parameter> */ private $parameters; /** * The index of the first result to retrieve. * * @var int */ private $firstResult = 0; /** * The maximum number of results to retrieve. * * @var int|null */ private $maxResults = null; /** * Keeps root entity alias names for join entities. * * @psalm-var array<string, string> */ private $joinRootAliases = []; /** * Whether to use second level cache, if available. * * @var bool */ protected $cacheable = false; /** * Second level cache region name. * * @var string|null */ protected $cacheRegion; /** * Second level query cache mode. * * @var int|null * @psalm-var Cache::MODE_*|null */ protected $cacheMode; /** @var int */ protected $lifetime = 0; /** * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>. * * @param EntityManagerInterface $em The EntityManager to use. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->parameters = new ArrayCollection(); } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * <code> * $qb = $em->createQueryBuilder(); * $qb * ->select('u') * ->from('User', 'u') * ->where($qb->expr()->eq('u.id', 1)); * </code> * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return Query\Expr */ public function expr() { return $this->em->getExpressionBuilder(); } /** * Enable/disable second level query (result) caching for this query. * * @param bool $cacheable * * @return $this */ public function setCacheable($cacheable) { $this->cacheable = (bool) $cacheable; return $this; } /** * Are the query results enabled for second level cache? * * @return bool */ public function isCacheable() { return $this->cacheable; } /** * @param string $cacheRegion * * @return $this */ public function setCacheRegion($cacheRegion) { $this->cacheRegion = (string) $cacheRegion; return $this; } /** * Obtain the name of the second level query cache region in which query results will be stored * * @return string|null The cache region name; NULL indicates the default region. */ public function getCacheRegion() { return $this->cacheRegion; } /** @return int */ public function getLifetime() { return $this->lifetime; } /** * Sets the life-time for this query into second level cache. * * @param int $lifetime * * @return $this */ public function setLifetime($lifetime) { $this->lifetime = (int) $lifetime; return $this; } /** * @return int|null * @psalm-return Cache::MODE_*|null */ public function getCacheMode() { return $this->cacheMode; } /** * @param int $cacheMode * @psalm-param Cache::MODE_* $cacheMode * * @return $this */ public function setCacheMode($cacheMode) { $this->cacheMode = (int) $cacheMode; return $this; } /** * Gets the type of the currently built query. * * @deprecated If necessary, track the type of the query being built outside of the builder. * * @return int * @psalm-return self::SELECT|self::DELETE|self::UPDATE */ public function getType() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/orm/pull/9945', 'Relying on the type of the query being built is deprecated.' . ' If necessary, track the type of the query being built outside of the builder.' ); return $this->type; } /** * Gets the associated EntityManager for this query builder. * * @return EntityManagerInterface */ public function getEntityManager() { return $this->em; } /** * Gets the state of this query builder instance. * * @deprecated The builder state is an internal concern. * * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @psalm-return self::STATE_* */ public function getState() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/orm/pull/9945', 'Relying on the query builder state is deprecated as it is an internal concern.' ); return $this->state; } /** * Gets the complete DQL string formed by the current specifications of this QueryBuilder. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * echo $qb->getDql(); // SELECT u FROM User u * </code> * * @return string The DQL query string. */ public function getDQL() { if ($this->dql !== null && $this->state === self::STATE_CLEAN) { return $this->dql; } switch ($this->type) { case self::DELETE: $dql = $this->getDQLForDelete(); break; case self::UPDATE: $dql = $this->getDQLForUpdate(); break; case self::SELECT: default: $dql = $this->getDQLForSelect(); break; } $this->state = self::STATE_CLEAN; $this->dql = $dql; return $dql; } /** * Constructs a Query instance from the current specifications of the builder. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * $q = $qb->getQuery(); * $results = $q->execute(); * </code> * * @return Query */ public function getQuery() { $parameters = clone $this->parameters; $query = $this->em->createQuery($this->getDQL()) ->setParameters($parameters) ->setFirstResult($this->firstResult) ->setMaxResults($this->maxResults); if ($this->lifetime) { $query->setLifetime($this->lifetime); } if ($this->cacheMode) { $query->setCacheMode($this->cacheMode); } if ($this->cacheable) { $query->setCacheable($this->cacheable); } if ($this->cacheRegion) { $query->setCacheRegion($this->cacheRegion); } return $query; } /** * Finds the root entity alias of the joined entity. * * @param string $alias The alias of the new join entity * @param string $parentAlias The parent entity alias of the join relationship */ private function findRootAlias(string $alias, string $parentAlias): string { if (in_array($parentAlias, $this->getRootAliases(), true)) { $rootAlias = $parentAlias; } elseif (isset($this->joinRootAliases[$parentAlias])) { $rootAlias = $this->joinRootAliases[$parentAlias]; } else { // Should never happen with correct joining order. Might be // thoughtful to throw exception instead. $rootAlias = $this->getRootAlias(); } $this->joinRootAliases[$alias] = $rootAlias; return $rootAlias; } /** * Gets the FIRST root alias of the query. This is the first entity alias involved * in the construction of the query. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * echo $qb->getRootAlias(); // u * </code> * * @deprecated Please use $qb->getRootAliases() instead. * * @return string * * @throws RuntimeException */ public function getRootAlias() { $aliases = $this->getRootAliases(); if (! isset($aliases[0])) { throw new RuntimeException('No alias was set before invoking getRootAlias().'); } return $aliases[0]; } /** * Gets the root aliases of the query. This is the entity aliases involved * in the construction of the query. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootAliases(); // array('u') * </code> * * @return string[] * @psalm-return list<string> */ public function getRootAliases() { $aliases = []; foreach ($this->dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); $from = substr($fromClause, 0, $spacePos); $alias = substr($fromClause, $spacePos + 1); $fromClause = new Query\Expr\From($from, $alias); } $aliases[] = $fromClause->getAlias(); } return $aliases; } /** * Gets all the aliases that have been used in the query. * Including all select root aliases and join aliases * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->join('u.articles','a'); * * $qb->getAllAliases(); // array('u','a') * </code> * * @return string[] * @psalm-return list<string> */ public function getAllAliases() { return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases)); } /** * Gets the root entities of the query. This is the entity aliases involved * in the construction of the query. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * * $qb->getRootEntities(); // array('User') * </code> * * @return string[] * @psalm-return list<string> */ public function getRootEntities() { $entities = []; foreach ($this->dqlParts['from'] as &$fromClause) { if (is_string($fromClause)) { $spacePos = strrpos($fromClause, ' '); $from = substr($fromClause, 0, $spacePos); $alias = substr($fromClause, $spacePos + 1); $fromClause = new Query\Expr\From($from, $alias); } $entities[] = $fromClause->getFrom(); } return $entities; } /** * Sets a query parameter for the query being constructed. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * </code> * * @param string|int $key The parameter position or name. * @param mixed $value The parameter value. * @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant * * @return $this */ public function setParameter($key, $value, $type = null) { $existingParameter = $this->getParameter($key); if ($existingParameter !== null) { $existingParameter->setValue($value, $type); return $this; } $this->parameters->add(new Parameter($key, $value, $type)); return $this; } /** * Sets a collection of query parameters for the query being constructed. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(new ArrayCollection(array( * new Parameter('user_id1', 1), * new Parameter('user_id2', 2) * ))); * </code> * * @param ArrayCollection|mixed[] $parameters The query parameters to set. * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters * * @return $this */ public function setParameters($parameters) { // BC compatibility with 2.3- if (is_array($parameters)) { /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */ $parameterCollection = new ArrayCollection(); foreach ($parameters as $key => $value) { $parameter = new Parameter($key, $value); $parameterCollection->add($parameter); } $parameters = $parameterCollection; } $this->parameters = $parameters; return $this; } /** * Gets all defined query parameters for the query being constructed. * * @return ArrayCollection The currently defined query parameters. * @psalm-return ArrayCollection<int, Parameter> */ public function getParameters() { return $this->parameters; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param string|int $key The key (index or name) of the bound parameter. * * @return Parameter|null The value of the bound parameter. */ public function getParameter($key) { $key = Parameter::normalizeName($key); $filteredParameters = $this->parameters->filter( static function (Parameter $parameter) use ($key): bool { $parameterName = $parameter->getName(); return $key === $parameterName; } ); return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param int|null $firstResult The first result to return. * * @return $this */ public function setFirstResult($firstResult) { $this->firstResult = (int) $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. * * @return int|null The position of the first result. */ public function getFirstResult() { return $this->firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param int|null $maxResults The maximum number of results to retrieve. * * @return $this */ public function setMaxResults($maxResults) { if ($maxResults !== null) { $maxResults = (int) $maxResults; } $this->maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if {@link setMaxResults} was not applied to this query builder. * * @return int|null Maximum number of results. */ public function getMaxResults() { return $this->maxResults; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'join', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $dqlPartName The DQL part name. * @param string|object|array $dqlPart An Expr object. * @param bool $append Whether to append (true) or replace (false). * @psalm-param string|object|list<string>|array{join: array<int|string, object>} $dqlPart * * @return $this */ public function add($dqlPartName, $dqlPart, $append = false) { if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) { throw new InvalidArgumentException( "Using \$append = true does not have an effect with 'where' or 'having' " . 'parts. See QueryBuilder#andWhere() for an example for correct usage.' ); } $isMultiple = is_array($this->dqlParts[$dqlPartName]) && ! ($dqlPartName === 'join' && ! $append); // Allow adding any part retrieved from self::getDQLParts(). if (is_array($dqlPart) && $dqlPartName !== 'join') { $dqlPart = reset($dqlPart); } // This is introduced for backwards compatibility reasons. // TODO: Remove for 3.0 if ($dqlPartName === 'join') { $newDqlPart = []; foreach ($dqlPart as $k => $v) { $k = is_numeric($k) ? $this->getRootAlias() : $k; $newDqlPart[$k] = $v; } $dqlPart = $newDqlPart; } if ($append && $isMultiple) { if (is_array($dqlPart)) { $key = key($dqlPart); $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key]; } else { $this->dqlParts[$dqlPartName][] = $dqlPart; } } else { $this->dqlParts[$dqlPartName] = $isMultiple ? [$dqlPart] : $dqlPart; } $this->state = self::STATE_DIRTY; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * <code> * $qb = $em->createQueryBuilder() * ->select('u', 'p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * </code> * * @param mixed $select The selection expressions. * * @return $this */ public function select($select = null) { $this->type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), false); } /** * Adds a DISTINCT flag to this query. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->distinct() * ->from('User', 'u'); * </code> * * @param bool $flag * * @return $this */ public function distinct($flag = true) { $this->dqlParts['distinct'] = (bool) $flag; return $this; } /** * Adds an item that is to be returned in the query result. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->addSelect('p') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p'); * </code> * * @param mixed $select The selection expression. * * @return $this */ public function addSelect($select = null) { $this->type = self::SELECT; if (empty($select)) { return $this; } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', new Expr\Select($selects), true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain entity type. * * <code> * $qb = $em->createQueryBuilder() * ->delete('User', 'u') * ->where('u.id = :user_id') * ->setParameter('user_id', 1); * </code> * * @param string|null $delete The class/type whose instances are subject to the deletion. * @param string|null $alias The class/type alias used in the constructed query. * * @return $this */ public function delete($delete = null, $alias = null) { $this->type = self::DELETE; if (! $delete) { return $this; } if (! $alias) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9733', 'Omitting the alias is deprecated and will throw an exception in Doctrine 3.0.' ); } return $this->add('from', new Expr\From($delete, $alias)); } /** * Turns the query being built into a bulk update query that ranges over * a certain entity type. * * <code> * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', '?1') * ->where('u.id = ?2'); * </code> * * @param string|null $update The class/type whose instances are subject to the update. * @param string|null $alias The class/type alias used in the constructed query. * * @return $this */ public function update($update = null, $alias = null) { $this->type = self::UPDATE; if (! $update) { return $this; } if (! $alias) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/9733', 'Omitting the alias is deprecated and will throw an exception in Doctrine 3.0.' ); } return $this->add('from', new Expr\From($update, $alias)); } /** * Creates and adds a query root corresponding to the entity identified by the given alias, * forming a cartesian product with any existing query roots. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u'); * </code> * * @param string $from The class name. * @param string $alias The alias of the class. * @param string|null $indexBy The index for the from. * * @return $this */ public function from($from, $alias, $indexBy = null) { return $this->add('from', new Expr\From($from, $alias, $indexBy), true); } /** * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it * setting an index by. * * <code> * $qb = $userRepository->createQueryBuilder('u') * ->indexBy('u', 'u.id'); * * // Is equivalent to... * * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u', 'u.id'); * </code> * * @param string $alias The root alias of the class. * @param string $indexBy The index for the from. * * @return $this * * @throws Query\QueryException */ public function indexBy($alias, $indexBy) { $rootAliases = $this->getRootAliases(); if (! in_array($alias, $rootAliases, true)) { throw new Query\QueryException( sprintf('Specified root alias %s must be set before invoking indexBy().', $alias) ); } foreach ($this->dqlParts['from'] as &$fromClause) { assert($fromClause instanceof Expr\From); if ($fromClause->getAlias() !== $alias) { continue; } $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy); } return $this; } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * </code> * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType * * @return $this */ public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy); } /** * Creates and adds a join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * [php] * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType * * @return $this */ public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, (int) strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', [$rootAlias => $join], true); } /** * Creates and adds a left join over an entity association to the query. * * The entities in the joined association will be fetched as part of the query * result if the alias used for the joined association is placed in the select * expressions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); * </code> * * @param string $join The relationship to join. * @param string $alias The alias of the join. * @param string|null $conditionType The condition type constant. Either ON or WITH. * @param string|Expr\Comparison|Expr\Composite|Expr\Func|null $condition The condition for the join. * @param string|null $indexBy The index for the join. * @psalm-param Expr\Join::ON|Expr\Join::WITH|null $conditionType * * @return $this */ public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $parentAlias = substr($join, 0, (int) strpos($join, '.')); $rootAlias = $this->findRootAlias($alias, $parentAlias); $join = new Expr\Join( Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy ); return $this->add('join', [$rootAlias => $join], true); } /** * Sets a new value for a field in a bulk update query. * * <code> * $qb = $em->createQueryBuilder() * ->update('User', 'u') * ->set('u.password', '?1') * ->where('u.id = ?2'); * </code> * * @param string $key The key/field to set. * @param mixed $value The value, expression, placeholder, etc. * * @return $this */ public function set($key, $value) { return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = ?'); * * // You can optionally programmatically build and/or expressions * $qb = $em->createQueryBuilder(); * * $or = $qb->expr()->orX(); * $or->add($qb->expr()->eq('u.id', 1)); * $or->add($qb->expr()->eq('u.id', 2)); * * $qb->update('User', 'u') * ->set('u.password', '?') * ->where($or); * </code> * * @param mixed $predicates The restriction predicates. * * @return $this */ public function where($predicates) { if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) { $predicates = new Expr\Andx(func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * </code> * * @see where() * * @param mixed $where The query restrictions. * * @return $this */ public function andWhere() { $args = func_get_args(); $where = $this->getDQLPart('where'); if ($where instanceof Expr\Andx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Andx($args); } return $this->add('where', $where); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * </code> * * @see where() * * @param mixed $where The WHERE statement. * * @return $this */ public function orWhere() { $args = func_get_args(); $where = $this->getDQLPart('where'); if ($where instanceof Expr\Orx) { $where->addMultiple($args); } else { array_unshift($args, $where); $where = new Expr\Orx($args); } return $this->add('where', $where); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.id'); * </code> * * @param string $groupBy The grouping expression. * * @return $this */ public function groupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args())); } /** * Adds a grouping expression to the query. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * ->groupBy('u.lastLogin') * ->addGroupBy('u.createdAt'); * </code> * * @param string $groupBy The grouping expression. * * @return $this */ public function addGroupBy($groupBy) { return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * * @return $this */ public function having($having) { if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) { $having = new Expr\Andx(func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * * @return $this */ public function andHaving($having) { $args = func_get_args(); $having = $this->getDQLPart('having'); if ($having instanceof Expr\Andx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Andx($args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * * @return $this */ public function orHaving($having) { $args = func_get_args(); $having = $this->getDQLPart('having'); if ($having instanceof Expr\Orx) { $having->addMultiple($args); } else { array_unshift($args, $having); $having = new Expr\Orx($args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string|Expr\OrderBy $sort The ordering expression. * @param string|null $order The ordering direction. * * @return $this */ public function orderBy($sort, $order = null) { $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); return $this->add('orderBy', $orderBy); } /** * Adds an ordering to the query results. * * @param string|Expr\OrderBy $sort The ordering expression. * @param string|null $order The ordering direction. * * @return $this */ public function addOrderBy($sort, $order = null) { $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order); return $this->add('orderBy', $orderBy, true); } /** * Adds criteria to the query. * * Adds where expressions with AND operator. * Adds orderings. * Overrides firstResult and maxResults if they're set. * * @return $this * * @throws Query\QueryException */ public function addCriteria(Criteria $criteria) { $allAliases = $this->getAllAliases(); if (! isset($allAliases[0])) { throw new Query\QueryException('No aliases are set before invoking addCriteria().'); } $visitor = new QueryExpressionVisitor($this->getAllAliases()); $whereExpression = $criteria->getWhereExpression(); if ($whereExpression) { $this->andWhere($visitor->dispatch($whereExpression)); foreach ($visitor->getParameters() as $parameter) { $this->parameters->add($parameter); } } if ($criteria->getOrderings()) { foreach ($criteria->getOrderings() as $sort => $order) { $hasValidAlias = false; foreach ($allAliases as $alias) { if (str_starts_with($sort . '.', $alias . '.')) { $hasValidAlias = true; break; } } if (! $hasValidAlias) { $sort = $allAliases[0] . '.' . $sort; } $this->addOrderBy($sort, $order); } } // Overwrite limits only if they was set in criteria $firstResult = $criteria->getFirstResult(); if ($firstResult > 0) { $this->setFirstResult($firstResult); } $maxResults = $criteria->getMaxResults(); if ($maxResults !== null) { $this->setMaxResults($maxResults); } return $this; } /** * Gets a query part by its name. * * @param string $queryPartName * * @return mixed $queryPart */ public function getDQLPart($queryPartName) { return $this->dqlParts[$queryPartName]; } /** * Gets all query parts. * * @psalm-return array<string, mixed> $dqlParts */ public function getDQLParts() { return $this->dqlParts; } private function getDQLForDelete(): string { return 'DELETE' . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', ']) . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); } private function getDQLForUpdate(): string { return 'UPDATE' . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', ']) . $this->getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', ']) . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); } private function getDQLForSelect(): string { $dql = 'SELECT' . ($this->dqlParts['distinct'] === true ? ' DISTINCT' : '') . $this->getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']); $fromParts = $this->getDQLPart('from'); $joinParts = $this->getDQLPart('join'); $fromClauses = []; // Loop through all FROM clauses if (! empty($fromParts)) { $dql .= ' FROM '; foreach ($fromParts as $from) { $fromClause = (string) $from; if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) { foreach ($joinParts[$from->getAlias()] as $join) { $fromClause .= ' ' . ((string) $join); } } $fromClauses[] = $fromClause; } } $dql .= implode(', ', $fromClauses) . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE ']) . $this->getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', ']) . $this->getReducedDQLQueryPart('having', ['pre' => ' HAVING ']) . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']); return $dql; } /** @psalm-param array<string, mixed> $options */ private function getReducedDQLQueryPart(string $queryPartName, array $options = []): string { $queryPart = $this->getDQLPart($queryPartName); if (empty($queryPart)) { return $options['empty'] ?? ''; } return ($options['pre'] ?? '') . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart) . ($options['post'] ?? ''); } /** * Resets DQL parts. * * @param string[]|null $parts * @psalm-param list<string>|null $parts * * @return $this */ public function resetDQLParts($parts = null) { if ($parts === null) { $parts = array_keys($this->dqlParts); } foreach ($parts as $part) { $this->resetDQLPart($part); } return $this; } /** * Resets single DQL part. * * @param string $part * * @return $this */ public function resetDQLPart($part) { $this->dqlParts[$part] = is_array($this->dqlParts[$part]) ? [] : null; $this->state = self::STATE_DIRTY; return $this; } /** * Gets a string representation of this QueryBuilder which corresponds to * the final DQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getDQL(); } /** * Deep clones all expression objects in the DQL parts. * * @return void */ public function __clone() { foreach ($this->dqlParts as $part => $elements) { if (is_array($this->dqlParts[$part])) { foreach ($this->dqlParts[$part] as $idx => $element) { if (is_object($element)) { $this->dqlParts[$part][$idx] = clone $element; } } } elseif (is_object($elements)) { $this->dqlParts[$part] = clone $elements; } } $parameters = []; foreach ($this->parameters as $parameter) { $parameters[] = clone $parameter; } $this->parameters = new ArrayCollection($parameters); } } orm/lib/Doctrine/ORM/TransactionRequiredException.php 0000644 00000000760 15120025736 0016675 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\ORM\Exception\ORMException; /** * Is thrown when a transaction is required for the current operation, but there is none open. * * @link www.doctrine-project.com */ class TransactionRequiredException extends ORMException { /** @return TransactionRequiredException */ public static function transactionRequired() { return new self('An open transaction is required for this operation.'); } } orm/lib/Doctrine/ORM/UnexpectedResultException.php 0000644 00000000321 15120025736 0016203 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use Doctrine\ORM\Exception\ORMException; /** * Exception for a unexpected query result. */ class UnexpectedResultException extends ORMException { } orm/lib/Doctrine/ORM/UnitOfWork.php 0000644 00000405651 15120025736 0013107 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use BackedEnum; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\EventManager; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\LockMode; use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostRemoveEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Event\PreFlushEventArgs; use Doctrine\ORM\Event\PrePersistEventArgs; use Doctrine\ORM\Event\PreRemoveEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\UnexpectedAssociationValue; use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Internal\HydrationCompleteHandler; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\Reflection\ReflectionPropertiesGetter; use Doctrine\ORM\Persisters\Collection\CollectionPersister; use Doctrine\ORM\Persisters\Collection\ManyToManyPersister; use Doctrine\ORM\Persisters\Collection\OneToManyPersister; use Doctrine\ORM\Persisters\Entity\BasicEntityPersister; use Doctrine\ORM\Persisters\Entity\EntityPersister; use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister; use Doctrine\ORM\Persisters\Entity\SingleTablePersister; use Doctrine\ORM\Utility\IdentifierFlattener; use Doctrine\Persistence\Mapping\RuntimeReflectionService; use Doctrine\Persistence\NotifyPropertyChanged; use Doctrine\Persistence\ObjectManagerAware; use Doctrine\Persistence\PropertyChangedListener; use Doctrine\Persistence\Proxy; use Exception; use InvalidArgumentException; use RuntimeException; use Throwable; use UnexpectedValueException; use function array_combine; use function array_diff_key; use function array_filter; use function array_key_exists; use function array_map; use function array_merge; use function array_pop; use function array_sum; use function array_values; use function assert; use function count; use function current; use function func_get_arg; use function func_num_args; use function get_class; use function get_debug_type; use function implode; use function in_array; use function is_array; use function is_object; use function method_exists; use function reset; use function spl_object_id; use function sprintf; /** * The UnitOfWork is responsible for tracking changes to objects during an * "object-level" transaction and for writing out changes to the database * in the correct order. * * Internal note: This class contains highly performance-sensitive code. * * @psalm-import-type AssociationMapping from ClassMetadata */ class UnitOfWork implements PropertyChangedListener { /** * An entity is in MANAGED state when its persistence is managed by an EntityManager. */ public const STATE_MANAGED = 1; /** * An entity is new if it has just been instantiated (i.e. using the "new" operator) * and is not (yet) managed by an EntityManager. */ public const STATE_NEW = 2; /** * A detached entity is an instance with persistent state and identity that is not * (or no longer) associated with an EntityManager (and a UnitOfWork). */ public const STATE_DETACHED = 3; /** * A removed entity instance is an instance with a persistent identity, * associated with an EntityManager, whose persistent state will be deleted * on commit. */ public const STATE_REMOVED = 4; /** * Hint used to collect all primary keys of associated entities during hydration * and execute it in a dedicated query afterwards * * @see https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql */ public const HINT_DEFEREAGERLOAD = 'deferEagerLoad'; /** * The identity map that holds references to all managed entities that have * an identity. The entities are grouped by their class name. * Since all classes in a hierarchy must share the same identifier set, * we always take the root class name of the hierarchy. * * @var mixed[] * @psalm-var array<class-string, array<string, object>> */ private $identityMap = []; /** * Map of all identifiers of managed entities. * Keys are object ids (spl_object_id). * * @var mixed[] * @psalm-var array<int, array<string, mixed>> */ private $entityIdentifiers = []; /** * Map of the original entity data of managed entities. * Keys are object ids (spl_object_id). This is used for calculating changesets * at commit time. * * Internal note: Note that PHPs "copy-on-write" behavior helps a lot with memory usage. * A value will only really be copied if the value in the entity is modified * by the user. * * @psalm-var array<int, array<string, mixed>> */ private $originalEntityData = []; /** * Map of entity changes. Keys are object ids (spl_object_id). * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. * * @psalm-var array<int, array<string, array{mixed, mixed}>> */ private $entityChangeSets = []; /** * The (cached) states of any known entities. * Keys are object ids (spl_object_id). * * @psalm-var array<int, self::STATE_*> */ private $entityStates = []; /** * Map of entities that are scheduled for dirty checking at commit time. * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. * Keys are object ids (spl_object_id). * * @psalm-var array<class-string, array<int, mixed>> */ private $scheduledForSynchronization = []; /** * A list of all pending entity insertions. * * @psalm-var array<int, object> */ private $entityInsertions = []; /** * A list of all pending entity updates. * * @psalm-var array<int, object> */ private $entityUpdates = []; /** * Any pending extra updates that have been scheduled by persisters. * * @psalm-var array<int, array{object, array<string, array{mixed, mixed}>}> */ private $extraUpdates = []; /** * A list of all pending entity deletions. * * @psalm-var array<int, object> */ private $entityDeletions = []; /** * New entities that were discovered through relationships that were not * marked as cascade-persist. During flush, this array is populated and * then pruned of any entities that were discovered through a valid * cascade-persist path. (Leftovers cause an error.) * * Keys are OIDs, payload is a two-item array describing the association * and the entity. * * @var array<int, array{AssociationMapping, object}> indexed by respective object spl_object_id() */ private $nonCascadedNewDetectedEntities = []; /** * All pending collection deletions. * * @psalm-var array<int, PersistentCollection<array-key, object>> */ private $collectionDeletions = []; /** * All pending collection updates. * * @psalm-var array<int, PersistentCollection<array-key, object>> */ private $collectionUpdates = []; /** * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork. * At the end of the UnitOfWork all these collections will make new snapshots * of their data. * * @psalm-var array<int, PersistentCollection<array-key, object>> */ private $visitedCollections = []; /** * List of collections visited during the changeset calculation that contain to-be-removed * entities and need to have keys removed post commit. * * Indexed by Collection object ID, which also serves as the key in self::$visitedCollections; * values are the key names that need to be removed. * * @psalm-var array<int, array<array-key, true>> */ private $pendingCollectionElementRemovals = []; /** * The EntityManager that "owns" this UnitOfWork instance. * * @var EntityManagerInterface */ private $em; /** * The entity persister instances used to persist entity instances. * * @psalm-var array<string, EntityPersister> */ private $persisters = []; /** * The collection persister instances used to persist collections. * * @psalm-var array<array-key, CollectionPersister> */ private $collectionPersisters = []; /** * The EventManager used for dispatching events. * * @var EventManager */ private $evm; /** * The ListenersInvoker used for dispatching events. * * @var ListenersInvoker */ private $listenersInvoker; /** * The IdentifierFlattener used for manipulating identifiers * * @var IdentifierFlattener */ private $identifierFlattener; /** * Orphaned entities that are scheduled for removal. * * @psalm-var array<int, object> */ private $orphanRemovals = []; /** * Read-Only objects are never evaluated * * @var array<int, true> */ private $readOnlyObjects = []; /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. * * @psalm-var array<class-string, array<string, mixed>> */ private $eagerLoadingEntities = []; /** @var bool */ protected $hasCache = false; /** * Helper for handling completion of hydration * * @var HydrationCompleteHandler */ private $hydrationCompleteHandler; /** @var ReflectionPropertiesGetter */ private $reflectionPropertiesGetter; /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. */ public function __construct(EntityManagerInterface $em) { $this->em = $em; $this->evm = $em->getEventManager(); $this->listenersInvoker = new ListenersInvoker($em); $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); $this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory()); $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em); $this->reflectionPropertiesGetter = new ReflectionPropertiesGetter(new RuntimeReflectionService()); } /** * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. * * The operations are executed in the following order: * * 1) All entity insertions * 2) All entity updates * 3) All collection deletions * 4) All collection updates * 5) All entity deletions * * @param object|mixed[]|null $entity * * @return void * * @throws Exception */ public function commit($entity = null) { if ($entity !== null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8459', 'Calling %s() with any arguments to commit specific entities is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__ ); } $connection = $this->em->getConnection(); if ($connection instanceof PrimaryReadReplicaConnection) { $connection->ensureConnectedToPrimary(); } // Raise preFlush if ($this->evm->hasListeners(Events::preFlush)) { $this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em)); } // Compute changes done since last commit. if ($entity === null) { $this->computeChangeSets(); } elseif (is_object($entity)) { $this->computeSingleEntityChangeSet($entity); } elseif (is_array($entity)) { foreach ($entity as $object) { $this->computeSingleEntityChangeSet($object); } } if ( ! ($this->entityInsertions || $this->entityDeletions || $this->entityUpdates || $this->collectionUpdates || $this->collectionDeletions || $this->orphanRemovals) ) { $this->dispatchOnFlushEvent(); $this->dispatchPostFlushEvent(); $this->postCommitCleanup($entity); return; // Nothing to do. } $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations(); if ($this->orphanRemovals) { foreach ($this->orphanRemovals as $orphan) { $this->remove($orphan); } } $this->dispatchOnFlushEvent(); // Now we need a commit order to maintain referential integrity $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); $conn->beginTransaction(); try { // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { // Deferred explicit tracked collections can be removed only when owning relation was persisted $owner = $collectionToDelete->getOwner(); if ($this->em->getClassMetadata(get_class($owner))->isChangeTrackingDeferredImplicit() || $this->isScheduledForDirtyCheck($owner)) { $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); } } if ($this->entityInsertions) { foreach ($commitOrder as $class) { $this->executeInserts($class); } } if ($this->entityUpdates) { foreach ($commitOrder as $class) { $this->executeUpdates($class); } } // Extra updates that were requested by persisters. if ($this->extraUpdates) { $this->executeExtraUpdates(); } // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); } // Entity deletions come last and need to be in reverse commit order if ($this->entityDeletions) { for ($count = count($commitOrder), $i = $count - 1; $i >= 0 && $this->entityDeletions; --$i) { $this->executeDeletions($commitOrder[$i]); } } // Commit failed silently if ($conn->commit() === false) { $object = is_object($entity) ? $entity : null; throw new OptimisticLockException('Commit failed', $object); } } catch (Throwable $e) { $this->em->close(); if ($conn->isTransactionActive()) { $conn->rollBack(); } $this->afterTransactionRolledBack(); throw $e; } $this->afterTransactionComplete(); // Unset removed entities from collections, and take new snapshots from // all visited collections. foreach ($this->visitedCollections as $coid => $coll) { if (isset($this->pendingCollectionElementRemovals[$coid])) { foreach ($this->pendingCollectionElementRemovals[$coid] as $key => $valueIgnored) { unset($coll[$key]); } } $coll->takeSnapshot(); } $this->dispatchPostFlushEvent(); $this->postCommitCleanup($entity); } /** @param object|object[]|null $entity */ private function postCommitCleanup($entity): void { $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->extraUpdates = $this->collectionUpdates = $this->nonCascadedNewDetectedEntities = $this->collectionDeletions = $this->pendingCollectionElementRemovals = $this->visitedCollections = $this->orphanRemovals = []; if ($entity === null) { $this->entityChangeSets = $this->scheduledForSynchronization = []; return; } $entities = is_object($entity) ? [$entity] : $entity; foreach ($entities as $object) { $oid = spl_object_id($object); $this->clearEntityChangeSet($oid); unset($this->scheduledForSynchronization[$this->em->getClassMetadata(get_class($object))->rootEntityName][$oid]); } } /** * Computes the changesets of all entities scheduled for insertion. */ private function computeScheduleInsertsChangeSets(): void { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); $this->computeChangeSet($class, $entity); } } /** * Only flushes the given entity according to a ruleset that keeps the UoW consistent. * * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well! * 2. Read Only entities are skipped. * 3. Proxies are skipped. * 4. Only if entity is properly managed. * * @param object $entity * * @throws InvalidArgumentException */ private function computeSingleEntityChangeSet($entity): void { $state = $this->getEntityState($entity); if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) { throw new InvalidArgumentException('Entity has to be managed or scheduled for removal for single computation ' . self::objToStr($entity)); } $class = $this->em->getClassMetadata(get_class($entity)); if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) { $this->persist($entity); } // Compute changes for INSERTed entities first. This must always happen even in this case. $this->computeScheduleInsertsChangeSets(); if ($class->isReadOnly) { return; } // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized()) { return; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. $oid = spl_object_id($entity); if (! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } /** * Executes any extra updates that have been scheduled. */ private function executeExtraUpdates(): void { foreach ($this->extraUpdates as $oid => $update) { [$entity, $changeset] = $update; $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } $this->extraUpdates = []; } /** * Gets the changeset for an entity. * * @param object $entity * * @return mixed[][] * @psalm-return array<string, array{mixed, mixed}|PersistentCollection> */ public function & getEntityChangeSet($entity) { $oid = spl_object_id($entity); $data = []; if (! isset($this->entityChangeSets[$oid])) { return $data; } return $this->entityChangeSets[$oid]; } /** * Computes the changes that happened to a single entity. * * Modifies/populates the following properties: * * {@link _originalEntityData} * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) * then it was not fetched from the database and therefore we have no original * entity data yet. All of the current entity data is stored as the original entity data. * * {@link _entityChangeSets} * The changes detected on all properties of the entity are stored there. * A change is a tuple array where the first entry is the old value and the second * entry is the new value of the property. Changesets are used by persisters * to INSERT/UPDATE the persistent entity state. * * {@link _entityUpdates} * If the entity is already fully MANAGED (has been fetched from the database before) * and any changes to its properties are detected, then a reference to the entity is stored * there to mark it for an update. * * {@link _collectionDeletions} * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. * @psalm-param ClassMetadata<T> $class * @psalm-param T $entity * * @return void * * @template T of object * * @ignore */ public function computeChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_id($entity); if (isset($this->readOnlyObjects[$oid])) { return; } if (! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER; if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke); } $actualData = []; foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); if ($class->isCollectionValuedAssociation($name) && $value !== null) { if ($value instanceof PersistentCollection) { if ($value->getOwner() === $entity) { continue; } $value = new ArrayCollection($value->getValues()); } // If $value is not a Collection then use an ArrayCollection. if (! $value instanceof Collection) { $value = new ArrayCollection($value); } $assoc = $class->associationMappings[$name]; // Inject PersistentCollection $value = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); $value->setOwner($entity, $assoc); $value->setDirty(! $value->isEmpty()); $refProp->setValue($entity, $value); $actualData[$name] = $value; continue; } if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } } if (! isset($this->originalEntityData[$oid])) { // Entity is either NEW or MANAGED but not yet fully persisted (only has an id). // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = []; foreach ($actualData as $propName => $actualValue) { if (! isset($class->associationMappings[$propName])) { $changeSet[$propName] = [null, $actualValue]; continue; } $assoc = $class->associationMappings[$propName]; if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = [null, $actualValue]; } } $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); $changeSet = $isChangeTrackingNotify && isset($this->entityChangeSets[$oid]) ? $this->entityChangeSets[$oid] : []; foreach ($actualData as $propName => $actualValue) { // skip field, its a partially omitted one! if (! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { continue; } $orgValue = $originalData[$propName]; if (! empty($class->fieldMappings[$propName]['enumType'])) { if (is_array($orgValue)) { foreach ($orgValue as $id => $val) { if ($val instanceof BackedEnum) { $orgValue[$id] = $val->value; } } } else { if ($orgValue instanceof BackedEnum) { $orgValue = $orgValue->value; } } } // skip if value haven't changed if ($orgValue === $actualValue) { continue; } // if regular field if (! isset($class->associationMappings[$propName])) { if ($isChangeTrackingNotify) { continue; } $changeSet[$propName] = [$orgValue, $actualValue]; continue; } $assoc = $class->associationMappings[$propName]; // Persistent collection was exchanged with the "originally" // created one. This can only mean it was cloned and replaced // on another entity. if ($actualValue instanceof PersistentCollection) { $owner = $actualValue->getOwner(); if ($owner === null) { // cloned $actualValue->setOwner($entity, $assoc); } elseif ($owner !== $entity) { // no clone, we have to fix if (! $actualValue->isInitialized()) { $actualValue->initialize(); // we have to do this otherwise the cols share state } $newValue = clone $actualValue; $newValue->setOwner($entity, $assoc); $class->reflFields[$propName]->setValue($entity, $newValue); } } if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_id($orgValue); if (isset($this->collectionDeletions[$coid])) { continue; } $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. continue; } if ($assoc['type'] & ClassMetadata::TO_ONE) { if ($assoc['isOwningSide']) { $changeSet[$propName] = [$orgValue, $actualValue]; } if ($orgValue !== null && $assoc['orphanRemoval']) { assert(is_object($orgValue)); $this->scheduleOrphanRemoval($orgValue); } } } if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } // Look for changes in associations of the entity foreach ($class->associationMappings as $field => $assoc) { $val = $class->reflFields[$field]->getValue($entity); if ($val === null) { continue; } $this->computeAssociationChanges($assoc, $val); if ( ! isset($this->entityChangeSets[$oid]) && $assoc['isOwningSide'] && $assoc['type'] === ClassMetadata::MANY_TO_MANY && $val instanceof PersistentCollection && $val->isDirty() ) { $this->entityChangeSets[$oid] = []; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; } } } /** * Computes all the changes that have been done to entities and collections * since the last commit and stores these changes in the _entityChangeSet map * temporarily for access by the persisters, until the UoW commit is finished. * * @return void */ public function computeChangeSets() { // Compute changes for INSERTed entities first. This must always happen. $this->computeScheduleInsertsChangeSets(); // Compute changes for other MANAGED entities. Change tracking policies take effect here. foreach ($this->identityMap as $className => $entities) { $class = $this->em->getClassMetadata($className); // Skip class if instances are read-only if ($class->isReadOnly) { continue; } // If change tracking is explicit or happens through notification, then only compute // changes on entities of that type that are explicitly marked for synchronization. switch (true) { case $class->isChangeTrackingDeferredImplicit(): $entitiesToProcess = $entities; break; case isset($this->scheduledForSynchronization[$className]): $entitiesToProcess = $this->scheduledForSynchronization[$className]; break; default: $entitiesToProcess = []; } foreach ($entitiesToProcess as $entity) { // Ignore uninitialized proxy objects if ($entity instanceof Proxy && ! $entity->__isInitialized()) { continue; } // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here. $oid = spl_object_id($entity); if (! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } } } /** * Computes the changes of an association. * * @param mixed $value The value of the association. * @psalm-param AssociationMapping $assoc The association mapping. * * @throws ORMInvalidArgumentException * @throws ORMException */ private function computeAssociationChanges(array $assoc, $value): void { if ($value instanceof Proxy && ! $value->__isInitialized()) { return; } // If this collection is dirty, schedule it for updates if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_id($value); $this->collectionUpdates[$coid] = $value; $this->visitedCollections[$coid] = $value; } // Look through the entities, and in any of their associations, // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = $assoc['type'] & ClassMetadata::TO_ONE ? [$value] : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); foreach ($unwrappedValue as $key => $entry) { if (! ($entry instanceof $targetClass->name)) { throw ORMInvalidArgumentException::invalidAssociation($targetClass, $assoc, $entry); } $state = $this->getEntityState($entry, self::STATE_NEW); if (! ($entry instanceof $assoc['targetEntity'])) { throw UnexpectedAssociationValue::create( $assoc['sourceEntity'], $assoc['fieldName'], get_debug_type($entry), $assoc['targetEntity'] ); } switch ($state) { case self::STATE_NEW: if (! $assoc['isCascadePersist']) { /* * For now just record the details, because this may * not be an issue if we later discover another pathway * through the object-graph where cascade-persistence * is enabled for this object. */ $this->nonCascadedNewDetectedEntities[spl_object_id($entry)] = [$assoc, $entry]; break; } $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); break; case self::STATE_REMOVED: // Consume the $value as array (it's either an array or an ArrayAccess) // and remove the element from Collection. if (! ($assoc['type'] & ClassMetadata::TO_MANY)) { break; } $coid = spl_object_id($value); $this->visitedCollections[$coid] = $value; if (! isset($this->pendingCollectionElementRemovals[$coid])) { $this->pendingCollectionElementRemovals[$coid] = []; } $this->pendingCollectionElementRemovals[$coid][$key] = true; break; case self::STATE_DETACHED: // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc, $entry); default: // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. } } } /** * @param object $entity * @psalm-param ClassMetadata<T> $class * @psalm-param T $entity * * @template T of object */ private function persistNew(ClassMetadata $class, $entity): void { $oid = spl_object_id($entity); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new PrePersistEventArgs($entity, $this->em), $invoke); } $idGen = $class->idGenerator; if (! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generateId($this->em, $entity); if (! $idGen instanceof AssignedGenerator) { $idValue = [$class->getSingleIdentifierFieldName() => $this->convertSingleFieldIdentifierToPHPValue($class, $idValue)]; $class->setIdentifierValues($entity, $idValue); } // Some identifiers may be foreign keys to new entities. // In this case, we don't have the value yet and should treat it as if we have a post-insert generator if (! $this->hasMissingIdsWhichAreForeignKeys($class, $idValue)) { $this->entityIdentifiers[$oid] = $idValue; } } $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); } /** @param mixed[] $idValue */ private function hasMissingIdsWhichAreForeignKeys(ClassMetadata $class, array $idValue): bool { foreach ($idValue as $idField => $idFieldValue) { if ($idFieldValue === null && isset($class->associationMappings[$idField])) { return true; } } return false; } /** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. * * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. * @psalm-param ClassMetadata<T> $class * @psalm-param T $entity * * @return void * * @throws ORMInvalidArgumentException If the passed entity is not MANAGED. * * @template T of object * @ignore */ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_id($entity); if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; } if (! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } $actualData = []; foreach ($class->reflFields as $name => $refProp) { if ( ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField) && ! $class->isCollectionValuedAssociation($name) ) { $actualData[$name] = $refProp->getValue($entity); } } if (! isset($this->originalEntityData[$oid])) { throw new RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.'); } $originalData = $this->originalEntityData[$oid]; $changeSet = []; foreach ($actualData as $propName => $actualValue) { $orgValue = $originalData[$propName] ?? null; if ($orgValue !== $actualValue) { $changeSet[$propName] = [$orgValue, $actualValue]; } } if ($changeSet) { if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } elseif (! isset($this->entityInsertions[$oid])) { $this->entityChangeSets[$oid] = $changeSet; $this->entityUpdates[$oid] = $entity; } $this->originalEntityData[$oid] = $actualData; } } /** * Executes all entity insertions for entities of the specified type. */ private function executeInserts(ClassMetadata $class): void { $entities = []; $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); $insertionsForClass = []; foreach ($this->entityInsertions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $insertionsForClass[$oid] = $entity; $persister->addInsert($entity); unset($this->entityInsertions[$oid]); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $entities[] = $entity; } } $postInsertIds = $persister->executeInserts(); if ($postInsertIds) { // Persister returned post-insert IDs foreach ($postInsertIds as $postInsertId) { $idField = $class->getSingleIdentifierFieldName(); $idValue = $this->convertSingleFieldIdentifierToPHPValue($class, $postInsertId['generatedId']); $entity = $postInsertId['entity']; $oid = spl_object_id($entity); $class->reflFields[$idField]->setValue($entity, $idValue); $this->entityIdentifiers[$oid] = [$idField => $idValue]; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $idValue; $this->addToIdentityMap($entity); } } else { foreach ($insertionsForClass as $oid => $entity) { if (! isset($this->entityIdentifiers[$oid])) { //entity was not added to identity map because some identifiers are foreign keys to new entities. //add it now $this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity); } } } foreach ($entities as $entity) { $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new PostPersistEventArgs($entity, $this->em), $invoke); } } /** * @param object $entity * @psalm-param ClassMetadata<T> $class * @psalm-param T $entity * * @template T of object */ private function addToEntityIdentifiersAndEntityMap( ClassMetadata $class, int $oid, $entity ): void { $identifier = []; foreach ($class->getIdentifierFieldNames() as $idField) { $origValue = $class->getFieldValue($entity, $idField); $value = null; if (isset($class->associationMappings[$idField])) { // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. $value = $this->getSingleIdentifierValue($origValue); } $identifier[$idField] = $value ?? $origValue; $this->originalEntityData[$oid][$idField] = $origValue; } $this->entityStates[$oid] = self::STATE_MANAGED; $this->entityIdentifiers[$oid] = $identifier; $this->addToIdentityMap($entity); } /** * Executes all entity updates for entities of the specified type. */ private function executeUpdates(ClassMetadata $class): void { $className = $class->name; $persister = $this->getEntityPersister($className); $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate); foreach ($this->entityUpdates as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } if ($preUpdateInvoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke); $this->recomputeSingleEntityChangeSet($class, $entity); } if (! empty($this->entityChangeSets[$oid])) { $persister->update($entity); } unset($this->entityUpdates[$oid]); if ($postUpdateInvoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new PostUpdateEventArgs($entity, $this->em), $postUpdateInvoke); } } } /** * Executes all entity deletions for entities of the specified type. */ private function executeDeletions(ClassMetadata $class): void { $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->delete($entity); unset( $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid] ); // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; if (! $class->isIdentifierNatural()) { $class->reflFields[$class->identifier[0]]->setValue($entity, null); } if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new PostRemoveEventArgs($entity, $this->em), $invoke); } } } /** * Gets the commit order. * * @return list<ClassMetadata> */ private function getCommitOrder(): array { $calc = $this->getCommitOrderCalculator(); // See if there are any new classes in the changeset, that are not in the // commit order graph yet (don't have a node). // We have to inspect changeSet to be able to correctly build dependencies. // It is not possible to use IdentityMap here because post inserted ids // are not yet available. $newNodes = []; foreach (array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); if ($calc->hasNode($class->name)) { continue; } $calc->addNode($class->name, $class); $newNodes[] = $class; } // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); if (! $calc->hasNode($targetClass->name)) { $calc->addNode($targetClass->name, $targetClass); $newNodes[] = $targetClass; } $joinColumns = reset($assoc['joinColumns']); $calc->addDependency($targetClass->name, $class->name, (int) empty($joinColumns['nullable'])); // If the target class has mapped subclasses, these share the same dependency. if (! $targetClass->subClasses) { continue; } foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); if (! $calc->hasNode($subClassName)) { $calc->addNode($targetSubClass->name, $targetSubClass); $newNodes[] = $targetSubClass; } $calc->addDependency($targetSubClass->name, $class->name, 1); } } } return $calc->sort(); } /** * Schedules an entity for insertion into the database. * If the entity already has an identifier, it will be added to the identity map. * * @param object $entity The entity to schedule for insertion. * * @return void * * @throws ORMInvalidArgumentException * @throws InvalidArgumentException */ public function scheduleForInsert($entity) { $oid = spl_object_id($entity); if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException('Dirty entity can not be scheduled for insertion.'); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity); } if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity); } if (isset($this->entityInsertions[$oid])) { throw ORMInvalidArgumentException::scheduleInsertTwice($entity); } $this->entityInsertions[$oid] = $entity; if (isset($this->entityIdentifiers[$oid])) { $this->addToIdentityMap($entity); } if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } } /** * Checks whether an entity is scheduled for insertion. * * @param object $entity * * @return bool */ public function isScheduledForInsert($entity) { return isset($this->entityInsertions[spl_object_id($entity)]); } /** * Schedules an entity for being updated. * * @param object $entity The entity to schedule for being updated. * * @return void * * @throws ORMInvalidArgumentException */ public function scheduleForUpdate($entity) { $oid = spl_object_id($entity); if (! isset($this->entityIdentifiers[$oid])) { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'scheduling for update'); } if (isset($this->entityDeletions[$oid])) { throw ORMInvalidArgumentException::entityIsRemoved($entity, 'schedule for update'); } if (! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) { $this->entityUpdates[$oid] = $entity; } } /** * INTERNAL: * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. * * Extra updates for entities are stored as (entity, changeset) tuples. * * @param object $entity The entity for which to schedule an extra update. * @psalm-param array<string, array{mixed, mixed}> $changeset The changeset of the entity (what to update). * * @return void * * @ignore */ public function scheduleExtraUpdate($entity, array $changeset) { $oid = spl_object_id($entity); $extraUpdate = [$entity, $changeset]; if (isset($this->extraUpdates[$oid])) { [, $changeset2] = $this->extraUpdates[$oid]; $extraUpdate = [$entity, $changeset + $changeset2]; } $this->extraUpdates[$oid] = $extraUpdate; } /** * Checks whether an entity is registered as dirty in the unit of work. * Note: Is not very useful currently as dirty entities are only registered * at commit time. * * @param object $entity * * @return bool */ public function isScheduledForUpdate($entity) { return isset($this->entityUpdates[spl_object_id($entity)]); } /** * Checks whether an entity is registered to be checked in the unit of work. * * @param object $entity * * @return bool */ public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_id($entity)]); } /** * INTERNAL: * Schedules an entity for deletion. * * @param object $entity * * @return void */ public function scheduleForDelete($entity) { $oid = spl_object_id($entity); if (isset($this->entityInsertions[$oid])) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset($this->entityInsertions[$oid], $this->entityStates[$oid]); return; // entity has not been persisted yet, so nothing more to do. } if (! $this->isInIdentityMap($entity)) { return; } $this->removeFromIdentityMap($entity); unset($this->entityUpdates[$oid]); if (! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; $this->entityStates[$oid] = self::STATE_REMOVED; } } /** * Checks whether an entity is registered as removed/deleted with the unit * of work. * * @param object $entity * * @return bool */ public function isScheduledForDelete($entity) { return isset($this->entityDeletions[spl_object_id($entity)]); } /** * Checks whether an entity is scheduled for insertion, update or deletion. * * @param object $entity * * @return bool */ public function isEntityScheduled($entity) { $oid = spl_object_id($entity); return isset($this->entityInsertions[$oid]) || isset($this->entityUpdates[$oid]) || isset($this->entityDeletions[$oid]); } /** * INTERNAL: * Registers an entity in the identity map. * Note that entities in a hierarchy are registered with the class name of * the root entity. * * @param object $entity The entity to register. * * @return bool TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. * * @throws ORMInvalidArgumentException * * @ignore */ public function addToIdentityMap($entity) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = $this->getIdHashByEntity($entity); $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { return false; } $this->identityMap[$className][$idHash] = $entity; return true; } /** * Gets the id hash of an entity by its identifier. * * @param array<string|int, mixed> $identifier The identifier of an entity * * @return string The entity id hash. */ final public static function getIdHashByIdentifier(array $identifier): string { return implode( ' ', array_map( static function ($value) { if ($value instanceof BackedEnum) { return $value->value; } return $value; }, $identifier ) ); } /** * Gets the id hash of an entity. * * @param object $entity The entity managed by Unit Of Work * * @return string The entity id hash. */ public function getIdHashByEntity($entity): string { $identifier = $this->entityIdentifiers[spl_object_id($entity)]; if (empty($identifier) || in_array(null, $identifier, true)) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); } return self::getIdHashByIdentifier($identifier); } /** * Gets the state of an entity with regard to the current unit of work. * * @param object $entity * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). * This parameter can be set to improve performance of entity state detection * by potentially avoiding a database lookup if the distinction between NEW and DETACHED * is either known or does not matter for the caller of the method. * @psalm-param self::STATE_*|null $assume * * @return int The entity state. * @psalm-return self::STATE_* */ public function getEntityState($entity, $assume = null) { $oid = spl_object_id($entity); if (isset($this->entityStates[$oid])) { return $this->entityStates[$oid]; } if ($assume !== null) { return $assume; } // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. // Note that you can not remember the NEW or DETACHED state in _entityStates since // the UoW does not hold references to such objects and the object hash can be reused. // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. $class = $this->em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); if (! $id) { return self::STATE_NEW; } if ($class->containsForeignIdentifier || $class->containsEnumIdentifier) { $id = $this->identifierFlattener->flattenIdentifier($class, $id); } switch (true) { case $class->isIdentifierNatural(): // Check for a version field, if available, to avoid a db lookup. if ($class->isVersioned) { assert($class->versionField !== null); return $class->getFieldValue($entity, $class->versionField) ? self::STATE_DETACHED : self::STATE_NEW; } // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; case ! $class->idGenerator->isPostInsertGenerator(): // if we have a pre insert generator we can't be sure that having an id // really means that the entity exists. We have to verify this through // the last resort: a db lookup // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } // db lookup if ($this->getEntityPersister($class->name)->exists($entity)) { return self::STATE_DETACHED; } return self::STATE_NEW; default: return self::STATE_DETACHED; } } /** * INTERNAL: * Removes an entity from the identity map. This effectively detaches the * entity from the persistence management of Doctrine. * * @param object $entity * * @return bool * * @throws ORMInvalidArgumentException * * @ignore */ public function removeFromIdentityMap($entity) { $oid = spl_object_id($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); if ($idHash === '') { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'remove from identity map'); } $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash], $this->readOnlyObjects[$oid]); //$this->entityStates[$oid] = self::STATE_DETACHED; return true; } return false; } /** * INTERNAL: * Gets an entity in the identity map by its identifier hash. * * @param string $idHash * @param string $rootClassName * * @return object * * @ignore */ public function getByIdHash($idHash, $rootClassName) { return $this->identityMap[$rootClassName][$idHash]; } /** * INTERNAL: * Tries to get an entity by its identifier hash. If no entity is found for * the given hash, FALSE is returned. * * @param mixed $idHash (must be possible to cast it to string) * @param string $rootClassName * * @return false|object The found entity or FALSE. * * @ignore */ public function tryGetByIdHash($idHash, $rootClassName) { $stringIdHash = (string) $idHash; return $this->identityMap[$rootClassName][$stringIdHash] ?? false; } /** * Checks whether an entity is registered in the identity map of this UnitOfWork. * * @param object $entity * * @return bool */ public function isInIdentityMap($entity) { $oid = spl_object_id($entity); if (empty($this->entityIdentifiers[$oid])) { return false; } $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } /** * INTERNAL: * Checks whether an identifier hash exists in the identity map. * * @param string $idHash * @param string $rootClassName * * @return bool * * @ignore */ public function containsIdHash($idHash, $rootClassName) { return isset($this->identityMap[$rootClassName][$idHash]); } /** * Persists an entity as part of the current unit of work. * * @param object $entity The entity to persist. * * @return void */ public function persist($entity) { $visited = []; $this->doPersist($entity, $visited); } /** * Persists an entity as part of the current unit of work. * * This method is internally called during persist() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to persist. * @psalm-param array<int, object> $visited The already visited entities. * * @throws ORMInvalidArgumentException * @throws UnexpectedValueException */ private function doPersist($entity, array &$visited): void { $oid = spl_object_id($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // Mark visited $class = $this->em->getClassMetadata(get_class($entity)); // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation). // If we would detect DETACHED here we would throw an exception anyway with the same // consequences (not recoverable/programming error), so just assuming NEW here // lets us avoid some database lookups for entities with natural identifiers. $entityState = $this->getEntityState($entity, self::STATE_NEW); switch ($entityState) { case self::STATE_MANAGED: // Nothing to do, except if policy is "deferred explicit" if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } break; case self::STATE_NEW: $this->persistNew($class, $entity); break; case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); $this->addToIdentityMap($entity); $this->entityStates[$oid] = self::STATE_MANAGED; if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } break; case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'persisted'); default: throw new UnexpectedValueException(sprintf( 'Unexpected entity state: %s. %s', $entityState, self::objToStr($entity) )); } $this->cascadePersist($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * @param object $entity The entity to remove. * * @return void */ public function remove($entity) { $visited = []; $this->doRemove($entity, $visited); } /** * Deletes an entity as part of the current unit of work. * * This method is internally called during delete() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to delete. * @psalm-param array<int, object> $visited The map of the already visited entities. * * @throws ORMInvalidArgumentException If the instance is a detached entity. * @throws UnexpectedValueException */ private function doRemove($entity, array &$visited): void { $oid = spl_object_id($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited // Cascade first, because scheduleForDelete() removes the entity from the identity map, which // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; case self::STATE_MANAGED: $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new PreRemoveEventArgs($entity, $this->em), $invoke); } $this->scheduleForDelete($entity); break; case self::STATE_DETACHED: throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'removed'); default: throw new UnexpectedValueException(sprintf( 'Unexpected entity state: %s. %s', $entityState, self::objToStr($entity) )); } } /** * Merges the state of the given detached entity into this UnitOfWork. * * @deprecated 2.7 This method is being removed from the ORM and won't have any replacement * * @param object $entity * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. */ public function merge($entity) { $visited = []; return $this->doMerge($entity, $visited); } /** * Executes a merge operation on an entity. * * @param object $entity * @psalm-param AssociationMapping|null $assoc * @psalm-param array<int, object> $visited * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws ORMInvalidArgumentException If the entity instance is NEW. * @throws EntityNotFoundException if an assigned identifier is used in the entity, but none is provided. */ private function doMerge( $entity, array &$visited, $prevManagedCopy = null, ?array $assoc = null ) { $oid = spl_object_id($entity); if (isset($visited[$oid])) { $managedCopy = $visited[$oid]; if ($prevManagedCopy !== null) { $this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy); } return $managedCopy; } $class = $this->em->getClassMetadata(get_class($entity)); // First we assume DETACHED, although it can still be NEW but we can avoid // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. $managedCopy = $entity; if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); // If there is no ID, it is actually NEW. if (! $id) { $managedCopy = $this->newInstance($class); $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); $this->persistNew($class, $managedCopy); } else { $flatId = $class->containsForeignIdentifier || $class->containsEnumIdentifier ? $this->identifierFlattener->flattenIdentifier($class, $id) : $id; $managedCopy = $this->tryGetById($flatId, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) === self::STATE_REMOVED) { throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, 'merge'); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->em->find($class->name, $flatId); } if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. if (! $class->isIdentifierNatural()) { throw EntityNotFoundException::fromClassNameAndIdentifier( $class->getName(), $this->identifierFlattener->flattenIdentifier($class, $id) ); } $managedCopy = $this->newInstance($class); $class->setIdentifierValues($managedCopy, $id); $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); $this->persistNew($class, $managedCopy); } else { $this->ensureVersionMatch($class, $entity, $managedCopy); $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); } } $visited[$oid] = $managedCopy; // mark visited if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } } if ($prevManagedCopy !== null) { $this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy); } // Mark the managed copy visited as well $visited[spl_object_id($managedCopy)] = $managedCopy; $this->cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; } /** * @param object $entity * @param object $managedCopy * @psalm-param ClassMetadata<T> $class * @psalm-param T $entity * @psalm-param T $managedCopy * * @throws OptimisticLockException * * @template T of object */ private function ensureVersionMatch( ClassMetadata $class, $entity, $managedCopy ): void { if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) { return; } assert($class->versionField !== null); $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); // Throw exception if versions don't match. // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedEqualOperator if ($managedCopyVersion == $entityVersion) { return; } throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion); } /** * Tests if an entity is loaded - must either be a loaded proxy or not a proxy * * @param object $entity */ private function isLoaded($entity): bool { return ! ($entity instanceof Proxy) || $entity->__isInitialized(); } /** * Sets/adds associated managed copies into the previous entity's association field * * @param object $entity * @psalm-param AssociationMapping $association */ private function updateAssociationWithMergedEntity( $entity, array $association, $previousManagedCopy, $managedCopy ): void { $assocField = $association['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($previousManagedCopy)); if ($association['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($previousManagedCopy, $managedCopy); return; } $value = $prevClass->reflFields[$assocField]->getValue($previousManagedCopy); $value[] = $managedCopy; if ($association['type'] === ClassMetadata::ONE_TO_MANY) { $class = $this->em->getClassMetadata(get_class($entity)); $class->reflFields[$association['mappedBy']]->setValue($managedCopy, $previousManagedCopy); } } /** * Detaches an entity from the persistence management. It's persistence will * no longer be managed by Doctrine. * * @param object $entity The entity to detach. * * @return void */ public function detach($entity) { $visited = []; $this->doDetach($entity, $visited); } /** * Executes a detach operation on the given entity. * * @param object $entity * @param mixed[] $visited * @param bool $noCascade if true, don't cascade detach operation. */ private function doDetach( $entity, array &$visited, bool $noCascade = false ): void { $oid = spl_object_id($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited switch ($this->getEntityState($entity, self::STATE_DETACHED)) { case self::STATE_MANAGED: if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } unset( $this->entityInsertions[$oid], $this->entityUpdates[$oid], $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->entityStates[$oid], $this->originalEntityData[$oid] ); break; case self::STATE_NEW: case self::STATE_DETACHED: return; } if (! $noCascade) { $this->cascadeDetach($entity, $visited); } } /** * Refreshes the state of the given entity from the database, overwriting * any local, unpersisted changes. * * @param object $entity The entity to refresh * * @return void * * @throws InvalidArgumentException If the entity is not MANAGED. * @throws TransactionRequiredException */ public function refresh($entity) { $visited = []; $lockMode = null; if (func_num_args() > 1) { $lockMode = func_get_arg(1); } $this->doRefresh($entity, $visited, $lockMode); } /** * Executes a refresh operation on an entity. * * @param object $entity The entity to refresh. * @psalm-param array<int, object> $visited The already visited entities during cascades. * @psalm-param LockMode::*|null $lockMode * * @throws ORMInvalidArgumentException If the entity is not MANAGED. * @throws TransactionRequiredException */ private function doRefresh($entity, array &$visited, ?int $lockMode = null): void { switch (true) { case $lockMode === LockMode::PESSIMISTIC_READ: case $lockMode === LockMode::PESSIMISTIC_WRITE: if (! $this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } $oid = spl_object_id($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $this->getEntityPersister($class->name)->refresh( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $entity, $lockMode ); $this->cascadeRefresh($entity, $visited, $lockMode); } /** * Cascades a refresh operation to associated entities. * * @param object $entity * @psalm-param array<int, object> $visited * @psalm-param LockMode::*|null $lockMode */ private function cascadeRefresh($entity, array &$visited, ?int $lockMode = null): void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, static function ($assoc) { return $assoc['isCascadeRefresh']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): foreach ($relatedEntities as $relatedEntity) { $this->doRefresh($relatedEntity, $visited, $lockMode); } break; case $relatedEntities !== null: $this->doRefresh($relatedEntities, $visited, $lockMode); break; default: // Do nothing } } } /** * Cascades a detach operation to associated entities. * * @param object $entity * @param array<int, object> $visited */ private function cascadeDetach($entity, array &$visited): void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, static function ($assoc) { return $assoc['isCascadeDetach']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): foreach ($relatedEntities as $relatedEntity) { $this->doDetach($relatedEntity, $visited); } break; case $relatedEntities !== null: $this->doDetach($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades a merge operation to associated entities. * * @param object $entity * @param object $managedCopy * @psalm-param array<int, object> $visited */ private function cascadeMerge($entity, $managedCopy, array &$visited): void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, static function ($assoc) { return $assoc['isCascadeMerge']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if ($relatedEntities instanceof Collection) { if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { continue; } if ($relatedEntities instanceof PersistentCollection) { // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); } foreach ($relatedEntities as $relatedEntity) { $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); } } elseif ($relatedEntities !== null) { $this->doMerge($relatedEntities, $visited, $managedCopy, $assoc); } } } /** * Cascades the save operation to associated entities. * * @param object $entity * @psalm-param array<int, object> $visited */ private function cascadePersist($entity, array &$visited): void { if ($entity instanceof Proxy && ! $entity->__isInitialized()) { // nothing to do - proxy is not initialized, therefore we don't do anything with it return; } $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, static function ($assoc) { return $assoc['isCascadePersist']; } ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): if (($assoc['type'] & ClassMetadata::TO_MANY) <= 0) { throw ORMInvalidArgumentException::invalidAssociation( $this->em->getClassMetadata($assoc['targetEntity']), $assoc, $relatedEntities ); } foreach ($relatedEntities as $relatedEntity) { $this->doPersist($relatedEntity, $visited); } break; case $relatedEntities !== null: if (! $relatedEntities instanceof $assoc['targetEntity']) { throw ORMInvalidArgumentException::invalidAssociation( $this->em->getClassMetadata($assoc['targetEntity']), $assoc, $relatedEntities ); } $this->doPersist($relatedEntities, $visited); break; default: // Do nothing } } } /** * Cascades the delete operation to associated entities. * * @param object $entity * @psalm-param array<int, object> $visited */ private function cascadeRemove($entity, array &$visited): void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter( $class->associationMappings, static function ($assoc) { return $assoc['isCascadeRemove']; } ); $entitiesToCascade = []; foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && ! $entity->__isInitialized()) { $entity->__load(); } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (true) { case $relatedEntities instanceof Collection: case is_array($relatedEntities): // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { $entitiesToCascade[] = $relatedEntity; } break; case $relatedEntities !== null: $entitiesToCascade[] = $relatedEntities; break; default: // Do nothing } } foreach ($entitiesToCascade as $relatedEntity) { $this->doRemove($relatedEntity, $visited); } } /** * Acquire a lock on the given entity. * * @param object $entity * @param int|DateTimeInterface|null $lockVersion * @psalm-param LockMode::* $lockMode * * @throws ORMInvalidArgumentException * @throws TransactionRequiredException * @throws OptimisticLockException */ public function lock($entity, int $lockMode, $lockVersion = null): void { if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $class = $this->em->getClassMetadata(get_class($entity)); switch (true) { case $lockMode === LockMode::OPTIMISTIC: if (! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } if ($lockVersion === null) { return; } if ($entity instanceof Proxy && ! $entity->__isInitialized()) { $entity->__load(); } assert($class->versionField !== null); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion); } break; case $lockMode === LockMode::NONE: case $lockMode === LockMode::PESSIMISTIC_READ: case $lockMode === LockMode::PESSIMISTIC_WRITE: if (! $this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_id($entity); $this->getEntityPersister($class->name)->lock( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode ); break; default: // Do nothing } } /** * Gets the CommitOrderCalculator used by the UnitOfWork to order commits. * * @return CommitOrderCalculator */ public function getCommitOrderCalculator() { return new Internal\CommitOrderCalculator(); } /** * Clears the UnitOfWork. * * @param string|null $entityName if given, only entities of this type will get detached. * * @return void * * @throws ORMInvalidArgumentException if an invalid entity name is given. */ public function clear($entityName = null) { if ($entityName === null) { $this->identityMap = $this->entityIdentifiers = $this->originalEntityData = $this->entityChangeSets = $this->entityStates = $this->scheduledForSynchronization = $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->nonCascadedNewDetectedEntities = $this->collectionDeletions = $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = $this->pendingCollectionElementRemovals = $this->visitedCollections = $this->eagerLoadingEntities = $this->orphanRemovals = []; } else { Deprecation::triggerIfCalledFromOutside( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8460', 'Calling %s() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__ ); $this->clearIdentityMapForEntityName($entityName); $this->clearEntityInsertionsForEntityName($entityName); } if ($this->evm->hasListeners(Events::onClear)) { $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } /** * INTERNAL: * Schedules an orphaned entity for removal. The remove() operation will be * invoked on that entity at the beginning of the next commit of this * UnitOfWork. * * @param object $entity * * @return void * * @ignore */ public function scheduleOrphanRemoval($entity) { $this->orphanRemovals[spl_object_id($entity)] = $entity; } /** * INTERNAL: * Cancels a previously scheduled orphan removal. * * @param object $entity * * @return void * * @ignore */ public function cancelOrphanRemoval($entity) { unset($this->orphanRemovals[spl_object_id($entity)]); } /** * INTERNAL: * Schedules a complete collection for removal when this UnitOfWork commits. * * @return void */ public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_id($coll); // TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? unset($this->collectionUpdates[$coid]); $this->collectionDeletions[$coid] = $coll; } /** @return bool */ public function isCollectionScheduledForDeletion(PersistentCollection $coll) { return isset($this->collectionDeletions[spl_object_id($coll)]); } /** @return object */ private function newInstance(ClassMetadata $class) { $entity = $class->newInstance(); if ($entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } return $entity; } /** * INTERNAL: * Creates an entity. Used for reconstitution of persistent entities. * * Internal note: Highly performance-sensitive method. * * @param string $className The name of the entity class. * @param mixed[] $data The data for the entity. * @param mixed[] $hints Any hints to account for during reconstitution/lookup of the entity. * @psalm-param class-string $className * @psalm-param array<string, mixed> $hints * * @return object The managed entity instance. * * @ignore * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = []) { $class = $this->em->getClassMetadata($className); $id = $this->identifierFlattener->flattenIdentifier($class, $data); $idHash = self::getIdHashByIdentifier($id); if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_id($entity); if ( isset($hints[Query::HINT_REFRESH], $hints[Query::HINT_REFRESH_ENTITY]) ) { $unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]; if ( $unmanagedProxy !== $entity && $unmanagedProxy instanceof Proxy && $this->isIdentifierEquals($unmanagedProxy, $entity) ) { // We will hydrate the given un-managed proxy anyway: // continue work, but consider it the entity from now on $entity = $unmanagedProxy; } } if ($entity instanceof Proxy && ! $entity->__isInitialized()) { $entity->__setInitialized(true); } else { if ( ! isset($hints[Query::HINT_REFRESH]) || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity) ) { return $entity; } } // inject ObjectManager upon refresh. if ($entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } $this->originalEntityData[$oid] = $data; } else { $entity = $this->newInstance($class); $oid = spl_object_id($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; if (isset($hints[Query::HINT_READ_ONLY])) { $this->readOnlyObjects[$oid] = true; } } if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } foreach ($data as $field => $value) { if (isset($class->fieldMappings[$field])) { $class->reflFields[$field]->setValue($entity, $value); } } // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { unset($this->eagerLoadingEntities[$class->rootEntityName]); } // Properly initialize any unfetched associations, if partial objects are not allowed. if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issues/8471', 'Partial Objects are deprecated (here entity %s)', $className ); return $entity; } foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. if (isset($hints['fetchAlias'], $hints['fetched'][$hints['fetchAlias']][$field])) { continue; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); switch (true) { case $assoc['type'] & ClassMetadata::TO_ONE: if (! $assoc['isOwningSide']) { // use the given entity association if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) { $this->originalEntityData[$oid][$field] = $data[$field]; $class->reflFields[$field]->setValue($entity, $data[$field]); $targetClass->reflFields[$assoc['mappedBy']]->setValue($data[$field], $entity); continue 2; } // Inverse side of x-to-one can never be lazy $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); continue 2; } // use the entity association if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) { $class->reflFields[$field]->setValue($entity, $data[$field]); $this->originalEntityData[$oid][$field] = $data[$field]; break; } $associatedId = []; // TODO: Is this even computed right in all cases of composite keys? foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { $joinColumnValue = $data[$srcColumn] ?? null; if ($joinColumnValue !== null) { if ($joinColumnValue instanceof BackedEnum) { $joinColumnValue = $joinColumnValue->value; } if ($targetClass->containsForeignIdentifier) { $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; } else { $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; } } elseif ( $targetClass->containsForeignIdentifier && in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, true) ) { // the missing key is part of target's entity primary key $associatedId = []; break; } } if (! $associatedId) { // Foreign key is NULL $class->reflFields[$field]->setValue($entity, null); $this->originalEntityData[$oid][$field] = null; break; } if (! isset($hints['fetchMode'][$class->name][$field])) { $hints['fetchMode'][$class->name][$field] = $assoc['fetch']; } // Foreign key is set // Check identity map first // FIXME: Can break easily with composite keys if join column values are in // wrong order. The correct order is the one in ClassMetadata#identifier. $relatedIdHash = self::getIdHashByIdentifier($associatedId); switch (true) { case isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash]): $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; // If this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) // then we can append this entity for eager loading! if ( $hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER && isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite && $newValue instanceof Proxy && $newValue->__isInitialized() === false ) { $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); } break; case $targetClass->subClasses: // If it might be a subtype, it can not be lazy. There isn't even // a way to solve this with deferred eager loading, which means putting // an entity with subclasses at a *-to-one location is really bad! (performance-wise) $newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId); break; default: $normalizedAssociatedId = $this->normalizeIdentifier($targetClass, $associatedId); switch (true) { // We are negating the condition here. Other cases will assume it is valid! case $hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER: $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $normalizedAssociatedId); break; // Deferred eager load only works for single identifier classes case isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite: // TODO: Is there a faster approach? $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($normalizedAssociatedId); $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $normalizedAssociatedId); break; default: // TODO: This is very imperformant, ignore it? $newValue = $this->em->find($assoc['targetEntity'], $normalizedAssociatedId); break; } if ($newValue === null) { break; } // PERF: Inlined & optimized code from UnitOfWork#registerManaged() $newValueOid = spl_object_id($newValue); $this->entityIdentifiers[$newValueOid] = $associatedId; $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; if ( $newValue instanceof NotifyPropertyChanged && ( ! $newValue instanceof Proxy || $newValue->__isInitialized()) ) { $newValue->addPropertyChangedListener($this); } $this->entityStates[$newValueOid] = self::STATE_MANAGED; // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also! break; } $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE && $newValue !== null) { $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); } break; default: // Ignore if its a cached collection if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) { break; } // use the given collection if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) { $data[$field]->setOwner($entity, $assoc); $class->reflFields[$field]->setValue($entity, $data[$field]); $this->originalEntityData[$oid][$field] = $data[$field]; break; } // Inject collection $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); $pColl->setOwner($entity, $assoc); $pColl->setInitialized(false); $reflField = $class->reflFields[$field]; $reflField->setValue($entity, $pColl); if ($assoc['fetch'] === ClassMetadata::FETCH_EAGER) { $this->loadCollection($pColl); $pColl->takeSnapshot(); } $this->originalEntityData[$oid][$field] = $pColl; break; } } // defer invoking of postLoad event to hydration complete step $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity); return $entity; } /** @return void */ public function triggerEagerLoads() { if (! $this->eagerLoadingEntities) { return; } // avoid infinite recursion $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = []; foreach ($eagerLoadingEntities as $entityName => $ids) { if (! $ids) { continue; } $class = $this->em->getClassMetadata($entityName); $this->getEntityPersister($entityName)->loadAll( array_combine($class->identifier, [array_values($ids)]) ); } } /** * Initializes (loads) an uninitialized persistent collection of an entity. * * @param PersistentCollection $collection The collection to initialize. * * @return void * * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733. */ public function loadCollection(PersistentCollection $collection) { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; } $collection->setInitialized(true); } /** * Gets the identity map of the UnitOfWork. * * @psalm-return array<class-string, array<string, object>> */ public function getIdentityMap() { return $this->identityMap; } /** * Gets the original data of an entity. The original data is the data that was * present at the time the entity was reconstituted from the database. * * @param object $entity * * @return mixed[] * @psalm-return array<string, mixed> */ public function getOriginalEntityData($entity) { $oid = spl_object_id($entity); return $this->originalEntityData[$oid] ?? []; } /** * @param object $entity * @param mixed[] $data * * @return void * * @ignore */ public function setOriginalEntityData($entity, array $data) { $this->originalEntityData[spl_object_id($entity)] = $data; } /** * INTERNAL: * Sets a property value of the original data array of an entity. * * @param int $oid * @param string $property * @param mixed $value * * @return void * * @ignore */ public function setOriginalEntityProperty($oid, $property, $value) { $this->originalEntityData[$oid][$property] = $value; } /** * Gets the identifier of an entity. * The returned value is always an array of identifier values. If the entity * has a composite identifier then the identifier values are in the same * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames(). * * @param object $entity * * @return mixed[] The identifier values. */ public function getEntityIdentifier($entity) { if (! isset($this->entityIdentifiers[spl_object_id($entity)])) { throw EntityNotFoundException::noIdentifierFound(get_debug_type($entity)); } return $this->entityIdentifiers[spl_object_id($entity)]; } /** * Processes an entity instance to extract their identifier values. * * @param object $entity The entity instance. * * @return mixed A scalar value. * * @throws ORMInvalidArgumentException */ public function getSingleIdentifierValue($entity) { $class = $this->em->getClassMetadata(get_class($entity)); if ($class->isIdentifierComposite) { throw ORMInvalidArgumentException::invalidCompositeIdentifier(); } $values = $this->isInIdentityMap($entity) ? $this->getEntityIdentifier($entity) : $class->getIdentifierValues($entity); return $values[$class->identifier[0]] ?? null; } /** * Tries to find an entity with the given identifier in the identity map of * this UnitOfWork. * * @param mixed $id The entity identifier to look for. * @param string $rootClassName The name of the root class of the mapped entity hierarchy. * @psalm-param class-string $rootClassName * * @return object|false Returns the entity with the specified identifier if it exists in * this UnitOfWork, FALSE otherwise. */ public function tryGetById($id, $rootClassName) { $idHash = self::getIdHashByIdentifier((array) $id); return $this->identityMap[$rootClassName][$idHash] ?? false; } /** * Schedules an entity for dirty-checking at commit-time. * * @param object $entity The entity to schedule for dirty-checking. * * @return void * * @todo Rename: scheduleForSynchronization */ public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; $this->scheduledForSynchronization[$rootClassName][spl_object_id($entity)] = $entity; } /** * Checks whether the UnitOfWork has any pending insertions. * * @return bool TRUE if this UnitOfWork has pending insertions, FALSE otherwise. */ public function hasPendingInsertions() { return ! empty($this->entityInsertions); } /** * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the * number of entities in the identity map. * * @return int */ public function size() { return array_sum(array_map('count', $this->identityMap)); } /** * Gets the EntityPersister for an Entity. * * @param string $entityName The name of the Entity. * @psalm-param class-string $entityName * * @return EntityPersister */ public function getEntityPersister($entityName) { if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } $class = $this->em->getClassMetadata($entityName); switch (true) { case $class->isInheritanceTypeNone(): $persister = new BasicEntityPersister($this->em, $class); break; case $class->isInheritanceTypeSingleTable(): $persister = new SingleTablePersister($this->em, $class); break; case $class->isInheritanceTypeJoined(): $persister = new JoinedSubclassPersister($this->em, $class); break; default: throw new RuntimeException('No persister found for entity.'); } if ($this->hasCache && $class->cache !== null) { $persister = $this->em->getConfiguration() ->getSecondLevelCacheConfiguration() ->getCacheFactory() ->buildCachedEntityPersister($this->em, $persister, $class); } $this->persisters[$entityName] = $persister; return $this->persisters[$entityName]; } /** * Gets a collection persister for a collection-valued association. * * @psalm-param AssociationMapping $association * * @return CollectionPersister */ public function getCollectionPersister(array $association) { $role = isset($association['cache']) ? $association['sourceEntity'] . '::' . $association['fieldName'] : $association['type']; if (isset($this->collectionPersisters[$role])) { return $this->collectionPersisters[$role]; } $persister = $association['type'] === ClassMetadata::ONE_TO_MANY ? new OneToManyPersister($this->em) : new ManyToManyPersister($this->em); if ($this->hasCache && isset($association['cache'])) { $persister = $this->em->getConfiguration() ->getSecondLevelCacheConfiguration() ->getCacheFactory() ->buildCachedCollectionPersister($this->em, $persister, $association); } $this->collectionPersisters[$role] = $persister; return $this->collectionPersisters[$role]; } /** * INTERNAL: * Registers an entity as managed. * * @param object $entity The entity. * @param mixed[] $id The identifier values. * @param mixed[] $data The original entity data. * * @return void */ public function registerManaged($entity, array $id, array $data) { $oid = spl_object_id($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->addToIdentityMap($entity); if ($entity instanceof NotifyPropertyChanged && ( ! $entity instanceof Proxy || $entity->__isInitialized())) { $entity->addPropertyChangedListener($this); } } /** * INTERNAL: * Clears the property changeset of the entity with the given OID. * * @param int $oid The entity's OID. * * @return void */ public function clearEntityChangeSet($oid) { unset($this->entityChangeSets[$oid]); } /* PropertyChangedListener implementation */ /** * Notifies this UnitOfWork of a property change in an entity. * * @param object $sender The entity that owns the property. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property. * @param mixed $newValue The new value of the property. * * @return void */ public function propertyChanged($sender, $propertyName, $oldValue, $newValue) { $oid = spl_object_id($sender); $class = $this->em->getClassMetadata(get_class($sender)); $isAssocField = isset($class->associationMappings[$propertyName]); if (! $isAssocField && ! isset($class->fieldMappings[$propertyName])) { return; // ignore non-persistent fields } // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue]; if (! isset($this->scheduledForSynchronization[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($sender); } } /** * Gets the currently scheduled entity insertions in this UnitOfWork. * * @psalm-return array<int, object> */ public function getScheduledEntityInsertions() { return $this->entityInsertions; } /** * Gets the currently scheduled entity updates in this UnitOfWork. * * @psalm-return array<int, object> */ public function getScheduledEntityUpdates() { return $this->entityUpdates; } /** * Gets the currently scheduled entity deletions in this UnitOfWork. * * @psalm-return array<int, object> */ public function getScheduledEntityDeletions() { return $this->entityDeletions; } /** * Gets the currently scheduled complete collection deletions * * @psalm-return array<int, PersistentCollection<array-key, object>> */ public function getScheduledCollectionDeletions() { return $this->collectionDeletions; } /** * Gets the currently scheduled collection inserts, updates and deletes. * * @psalm-return array<int, PersistentCollection<array-key, object>> */ public function getScheduledCollectionUpdates() { return $this->collectionUpdates; } /** * Helper method to initialize a lazy loading proxy or persistent collection. * * @param object $obj * * @return void */ public function initializeObject($obj) { if ($obj instanceof Proxy) { $obj->__load(); return; } if ($obj instanceof PersistentCollection) { $obj->initialize(); } } /** * Helper method to show an object as string. * * @param object $obj */ private static function objToStr($obj): string { return method_exists($obj, '__toString') ? (string) $obj : get_debug_type($obj) . '@' . spl_object_id($obj); } /** * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit(). * * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information * on this object that might be necessary to perform a correct update. * * @param object $object * * @return void * * @throws ORMInvalidArgumentException */ public function markReadOnly($object) { if (! is_object($object) || ! $this->isInIdentityMap($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } $this->readOnlyObjects[spl_object_id($object)] = true; } /** * Is this entity read only? * * @param object $object * * @return bool * * @throws ORMInvalidArgumentException */ public function isReadOnly($object) { if (! is_object($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } return isset($this->readOnlyObjects[spl_object_id($object)]); } /** * Perform whatever processing is encapsulated here after completion of the transaction. */ private function afterTransactionComplete(): void { $this->performCallbackOnCachedPersister(static function (CachedPersister $persister) { $persister->afterTransactionComplete(); }); } /** * Perform whatever processing is encapsulated here after completion of the rolled-back. */ private function afterTransactionRolledBack(): void { $this->performCallbackOnCachedPersister(static function (CachedPersister $persister) { $persister->afterTransactionRolledBack(); }); } /** * Performs an action after the transaction. */ private function performCallbackOnCachedPersister(callable $callback): void { if (! $this->hasCache) { return; } foreach (array_merge($this->persisters, $this->collectionPersisters) as $persister) { if ($persister instanceof CachedPersister) { $callback($persister); } } } private function dispatchOnFlushEvent(): void { if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em)); } } private function dispatchPostFlushEvent(): void { if ($this->evm->hasListeners(Events::postFlush)) { $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em)); } } /** * Verifies if two given entities actually are the same based on identifier comparison * * @param object $entity1 * @param object $entity2 */ private function isIdentifierEquals($entity1, $entity2): bool { if ($entity1 === $entity2) { return true; } $class = $this->em->getClassMetadata(get_class($entity1)); if ($class !== $this->em->getClassMetadata(get_class($entity2))) { return false; } $oid1 = spl_object_id($entity1); $oid2 = spl_object_id($entity2); $id1 = $this->entityIdentifiers[$oid1] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity1)); $id2 = $this->entityIdentifiers[$oid2] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity2)); return $id1 === $id2 || self::getIdHashByIdentifier($id1) === self::getIdHashByIdentifier($id2); } /** @throws ORMInvalidArgumentException */ private function assertThatThereAreNoUnintentionallyNonPersistedAssociations(): void { $entitiesNeedingCascadePersist = array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions); $this->nonCascadedNewDetectedEntities = []; if ($entitiesNeedingCascadePersist) { throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships( array_values($entitiesNeedingCascadePersist) ); } } /** * @param object $entity * @param object $managedCopy * * @throws ORMException * @throws OptimisticLockException * @throws TransactionRequiredException */ private function mergeEntityStateIntoManagedCopy($entity, $managedCopy): void { if (! $this->isLoaded($entity)) { return; } if (! $this->isLoaded($managedCopy)) { $managedCopy->__load(); } $class = $this->em->getClassMetadata(get_class($entity)); foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) { $name = $prop->name; $prop->setAccessible(true); if (! isset($class->associationMappings[$name])) { if (! $class->isIdentifier($name)) { $prop->setValue($managedCopy, $prop->getValue($entity)); } } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2['type'] & ClassMetadata::TO_ONE) { $other = $prop->getValue($entity); if ($other === null) { $prop->setValue($managedCopy, null); } else { if ($other instanceof Proxy && ! $other->__isInitialized()) { // do not merge fields marked lazy that have not been fetched. continue; } if (! $assoc2['isCascadeMerge']) { if ($this->getEntityState($other) === self::STATE_DETACHED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { $other = $this->em->find($targetClass->name, $relatedId); } else { $other = $this->em->getProxyFactory()->getProxy( $assoc2['targetEntity'], $relatedId ); $this->registerManaged($other, $relatedId, []); } } $prop->setValue($managedCopy, $other); } } } else { $mergeCol = $prop->getValue($entity); if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. continue; } $managedCol = $prop->getValue($managedCopy); if (! $managedCol) { $managedCol = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc2['targetEntity']), new ArrayCollection() ); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); // clear and set dirty a managed collection if its not also the same collection to merge from. if (! $managedCol->isEmpty() && $managedCol !== $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(true); if ( $assoc2['isOwningSide'] && $assoc2['type'] === ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify() ) { $this->scheduleForDirtyCheck($managedCopy); } } } } } if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } } /** * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle. * Unit of work able to fire deferred events, related to loading events here. * * @internal should be called internally from object hydrators * * @return void */ public function hydrationComplete() { $this->hydrationCompleteHandler->hydrationComplete(); } private function clearIdentityMapForEntityName(string $entityName): void { if (! isset($this->identityMap[$entityName])) { return; } $visited = []; foreach ($this->identityMap[$entityName] as $entity) { $this->doDetach($entity, $visited, false); } } private function clearEntityInsertionsForEntityName(string $entityName): void { foreach ($this->entityInsertions as $hash => $entity) { // note: performance optimization - `instanceof` is much faster than a function call if ($entity instanceof $entityName && get_class($entity) === $entityName) { unset($this->entityInsertions[$hash]); } } } /** * @param mixed $identifierValue * * @return mixed the identifier after type conversion * * @throws MappingException if the entity has more than a single identifier. */ private function convertSingleFieldIdentifierToPHPValue(ClassMetadata $class, $identifierValue) { return $this->em->getConnection()->convertToPHPValue( $identifierValue, $class->getTypeOfField($class->getSingleIdentifierFieldName()) ); } /** * Given a flat identifier, this method will produce another flat identifier, but with all * association fields that are mapped as identifiers replaced by entity references, recursively. * * @param mixed[] $flatIdentifier * * @return array<string, mixed> */ private function normalizeIdentifier(ClassMetadata $targetClass, array $flatIdentifier): array { $normalizedAssociatedId = []; foreach ($targetClass->getIdentifierFieldNames() as $name) { if (! array_key_exists($name, $flatIdentifier)) { continue; } if (! $targetClass->isSingleValuedAssociation($name)) { $normalizedAssociatedId[$name] = $flatIdentifier[$name]; continue; } $targetIdMetadata = $this->em->getClassMetadata($targetClass->getAssociationTargetClass($name)); // Note: the ORM prevents using an entity with a composite identifier as an identifier association // therefore, reset($targetIdMetadata->identifier) is always correct $normalizedAssociatedId[$name] = $this->em->getReference( $targetIdMetadata->getName(), $this->normalizeIdentifier( $targetIdMetadata, [(string) reset($targetIdMetadata->identifier) => $flatIdentifier[$name]] ) ); } return $normalizedAssociatedId; } } orm/lib/Doctrine/ORM/Version.php 0000644 00000001702 15120025736 0012452 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\ORM; use function str_replace; use function strtolower; use function version_compare; /** * Class to store and retrieve the version of Doctrine * * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement * * @link www.doctrine-project.org */ class Version { /** * Current Doctrine Version */ public const VERSION = '2.7.1-DEV'; /** * Compares a Doctrine version with the current one. * * @param string $version Doctrine version to compare. * * @return int Returns -1 if older, 0 if it is the same, 1 if version * passed as argument is newer. */ public static function compare($version) { $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); $version = str_replace(' ', '', $version); return version_compare($version, $currentVersion); } } orm/.gitmodules 0000644 00000000416 15120025736 0007520 0 ustar 00 [submodule "docs/en/_theme"] path = docs/en/_theme url = git://github.com/doctrine/doctrine-sphinx-theme.git [submodule "lib/vendor/doctrine-build-common"] path = lib/vendor/doctrine-build-common url = git://github.com/doctrine/doctrine-build-common.git orm/LICENSE 0000644 00000002037 15120025736 0006351 0 ustar 00 Copyright (c) Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. orm/README.md 0000644 00000004133 15120025736 0006622 0 ustar 00 | [3.0.x][3.0] | [2.16.x][2.16] | [2.15.x][2.15] | |:----------------:|:----------------:|:----------:| | [![Build status][3.0 image]][3.0] | [![Build status][2.16 image]][2.16] | [![Build status][2.15 image]][2.15] | | [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.16 coverage image]][2.16 coverage] | [![Coverage Status][2.15 coverage image]][2.15 coverage] | [<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html) Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication. ## More resources: * [Website](http://www.doctrine-project.org) * [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html) [3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x [3.0]: https://github.com/doctrine/orm/tree/3.0.x [3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg [3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x [2.16 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.16.x [2.16]: https://github.com/doctrine/orm/tree/2.16.x [2.16 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.16.x/graph/badge.svg [2.16 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.16.x [2.15 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.15.x [2.15]: https://github.com/doctrine/orm/tree/2.15.x [2.15 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.15.x/graph/badge.svg [2.15 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.15.x orm/SECURITY.md 0000644 00000001445 15120025736 0007137 0 ustar 00 Security ======== The Doctrine library is operating very close to your database and as such needs to handle and make assumptions about SQL injection vulnerabilities. It is vital that you understand how Doctrine approaches security, because we cannot protect you from SQL injection. Please read the documentation chapter on Security in Doctrine DBAL and ORM to understand the assumptions we make. - [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html) - [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html) If you find a Security bug in Doctrine, please report it on Jira and change the Security Level to "Security Issues". It will be visible to Doctrine Core developers and you only. orm/UPGRADE.md 0000644 00000167563 15120025736 0006775 0 ustar 00 # Upgrade to 2.15 ## Deprecated configuring `JoinColumn` on the inverse side of one-to-one associations For one-to-one associations, the side using the `mappedBy` attribute is the inverse side. The owning side is the entity with the table containing the foreign key. Using `JoinColumn` configuration on the _inverse_ side now triggers a deprecation notice and will be an error in 3.0. ## Deprecated overriding fields or associations not declared in mapped superclasses As stated in the documentation, fields and associations may only be overridden when being inherited from mapped superclasses. Overriding them for parent entity classes now triggers a deprecation notice and will be an error in 3.0. ## Deprecated undeclared entity inheritance As soon as an entity class inherits from another entity class, inheritance has to be declared by adding the appropriate configuration for the root entity. ## Deprecated stubs for "concrete table inheritance" This third way of mapping class inheritance was never implemented. Code stubs are now deprecated and will be removed in 3.0. * `\Doctrine\ORM\Mapping\ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS` constant * `\Doctrine\ORM\Mapping\ClassMetadataInfo::isInheritanceTypeTablePerClass()` method * Using `TABLE_PER_CLASS` as the value for the `InheritanceType` attribute or annotation or in XML configuration files. # Upgrade to 2.14 ## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method. Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead. ## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator` The following public constants have been deprecated: * `CommitOrderCalculator::NOT_VISITED` * `CommitOrderCalculator::IN_PROGRESS` * `CommitOrderCalculator::VISITED` These constants were used for internal purposes. Relying on them is discouraged. ## Deprecated `Doctrine\ORM\Query\AST\InExpression` The AST parser will create a `InListExpression` or a `InSubselectExpression` when encountering an `IN ()` DQL expression instead of a generic `InExpression`. As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of `SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`. ## Deprecated constructing a `CacheKey` without `$hash` The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with an optional parameter `$hash`. That parameter will become mandatory in 3.0. ## Deprecated `AttributeDriver::$entityAnnotationClasses` If you need to change the behavior of `AttributeDriver::isTransient()`, override that method instead. ## Deprecated incomplete schema updates Using `orm:schema-tool:update` without passing the `--complete` flag is deprecated. Use schema asset filtering if you need to preserve assets not managed by DBAL. Likewise, calling `SchemaTool::updateSchema()` or `SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated. ## Deprecated annotation mapping driver. Please switch to one of the other mapping drivers. Native attributes which PHP supports since version 8.0 are probably your best option. As a consequence, the following methods are deprecated: - `ORMSetup::createAnnotationMetadataConfiguration` - `ORMSetup::createDefaultAnnotationDriver` The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well. All annotation/attribute classes implement `Doctrine\ORM\Mapping\MappingAttribute` now. ## Deprecated `Doctrine\ORM\Proxy\Proxy` interface. Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized. ## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class. It will be removed in 3.0. Use one of the dedicated event classes instead: * `Doctrine\ORM\Event\PrePersistEventArgs` * `Doctrine\ORM\Event\PreUpdateEventArgs` * `Doctrine\ORM\Event\PreRemoveEventArgs` * `Doctrine\ORM\Event\PostPersistEventArgs` * `Doctrine\ORM\Event\PostUpdateEventArgs` * `Doctrine\ORM\Event\PostRemoveEventArgs` * `Doctrine\ORM\Event\PostLoadEventArgs` # Upgrade to 2.13 ## Deprecated `EntityManager::create()` The constructor of `EntityManager` is now public and should be used instead of the `create()` method. However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters. You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the connection. ## Deprecated `QueryBuilder` methods and constants. 1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern. 2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated. If necessary, track the type of the query being built outside of the builder. The following `QueryBuilder` constants related to the above methods have been deprecated: 1. `SELECT`, 2. `DELETE`, 3. `UPDATE`, 4. `STATE_DIRTY`, 5. `STATE_CLEAN`. ## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted. ### Before ```php $qb = $em->createQueryBuilder() ->delete('User u') ->where('u.id = :user_id') ->setParameter('user_id', 1); ``` ### After ```php $qb = $em->createQueryBuilder() ->delete('User', 'u') ->where('u.id = :user_id') ->setParameter('user_id', 1); ``` ## Deprecated using the `IDENTITY` identifier strategy on platform that do not support identity columns If identity columns are emulated with sequences on the platform you are using, you should switch to the `SEQUENCE` strategy. ## Deprecated passing `null` to `Doctrine\ORM\Query::setFirstResult()` `$query->setFirstResult(null);` is equivalent to `$query->setFirstResult(0)`. ## Deprecated calling setters without arguments The following methods will require an argument in 3.0. Pass `null` instead of omitting the argument. * `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()` * `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()` * `Doctrine\ORM\AbstractQuery::setResultCache()` * `Doctrine\ORM\AbstractQuery::setResultCacheProfile()` ## Deprecated passing invalid fetch modes to `AbstractQuery::setFetchMode()` Calling `AbstractQuery::setFetchMode()` with anything else than `Doctrine\ORM\Mapping::FETCH_EAGER` results in `Doctrine\ORM\Mapping::FETCH_LAZY` being used. Relying on that behavior is deprecated and will result in an exception in 3.0. ## Deprecated `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs` This method has been deprecated in: * `Doctrine\ORM\Event\OnClearEventArgs` * `Doctrine\ORM\Event\OnFlushEventArgs` * `Doctrine\ORM\Event\PostFlushEventArgs` * `Doctrine\ORM\Event\PreFlushEventArgs` It will be removed in 3.0. Use `getObjectManager()` instead. ## Prepare split of output walkers and tree walkers In 3.0, `SqlWalker` and its child classes won't implement the `TreeWalker` interface anymore. Relying on that inheritance is deprecated. The following methods of the `TreeWalker` interface have been deprecated: * `setQueryComponent()` * `walkSelectClause()` * `walkFromClause()` * `walkFunction()` * `walkOrderByClause()` * `walkOrderByItem()` * `walkHavingClause()` * `walkJoin()` * `walkSelectExpression()` * `walkQuantifiedExpression()` * `walkSubselect()` * `walkSubselectFromClause()` * `walkSimpleSelectClause()` * `walkSimpleSelectExpression()` * `walkAggregateExpression()` * `walkGroupByClause()` * `walkGroupByItem()` * `walkDeleteClause()` * `walkUpdateClause()` * `walkUpdateItem()` * `walkWhereClause()` * `walkConditionalExpression()` * `walkConditionalTerm()` * `walkConditionalFactor()` * `walkConditionalPrimary()` * `walkExistsExpression()` * `walkCollectionMemberExpression()` * `walkEmptyCollectionComparisonExpression()` * `walkNullComparisonExpression()` * `walkInExpression()` * `walkInstanceOfExpression()` * `walkLiteral()` * `walkBetweenExpression()` * `walkLikeExpression()` * `walkStateFieldPathExpression()` * `walkComparisonExpression()` * `walkInputParameter()` * `walkArithmeticExpression()` * `walkArithmeticTerm()` * `walkStringPrimary()` * `walkArithmeticFactor()` * `walkSimpleArithmeticExpression()` * `walkPathExpression()` * `walkResultVariable()` * `getExecutor()` The following changes have been made to the abstract `TreeWalkerAdapter` class: * All implementations of now-deprecated `TreeWalker` methods have been deprecated as well. * The method `setQueryComponent()` will become protected in 3.0. Calling it publicly is deprecated. * The method `_getQueryComponents()` is deprecated, call `getQueryComponents()` instead. On the `TreeWalkerChain` class, all implementations of now-deprecated `TreeWalker` methods have been deprecated as well. However, `SqlWalker` is unaffected by those deprecations and will continue to implement all of those methods. ## Deprecated passing `null` to `Doctrine\ORM\Query::setDQL()` Doing `$query->setDQL(null);` achieves nothing. ## Deprecated omitting second argument to `NamingStrategy::joinColumnName` When implementing `NamingStrategy`, it is deprecated to implement `joinColumnName()` with only one argument. ### Before ```php <?php class MyStrategy implements NamingStrategy { /** * @param string $propertyName A property name. */ public function joinColumnName($propertyName): string { // … } } ``` ### After For backward-compatibility reasons, the parameter has to be optional, but can be documented as guaranteed to be a `class-string`. ```php <?php class MyStrategy implements NamingStrategy { /** * @param string $propertyName A property name. * @param class-string $className */ public function joinColumnName($propertyName, $className = null): string { // … } } ``` ## Deprecated methods related to named queries The following methods have been deprecated: - `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryMapping()` - `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultClassMapping()` - `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultSetMapping()` - `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryEntityResultMapping()` ## Deprecated classes related to Doctrine 1 and reverse engineering The following classes have been deprecated: - `Doctrine\ORM\Tools\ConvertDoctrine1Schema` - `Doctrine\ORM\Tools\DisconnectedClassMetadataFactory` ## Deprecate `ClassMetadataInfo` usage It is deprecated to pass `Doctrine\ORM\Mapping\ClassMetadataInfo` instances that are not also instances of `Doctrine\ORM\ClassMetadata` to the following methods: - `Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder::__construct()` - `Doctrine\ORM\Mapping\Driver\DatabaseDriver::loadMetadataForClass()` - `Doctrine\ORM\Tools\SchemaValidator::validateClass()` # Upgrade to 2.12 ## Deprecated the `doctrine` binary. The documentation explains how the console tools can be bootstrapped for standalone usage. The method `ConsoleRunner::printCliConfigTemplate()` is deprecated because it was only useful in the context of the `doctrine` binary. ## Deprecate omitting `$class` argument to `ORMInvalidArgumentException::invalidIdentifierBindingEntity()` To make it easier to identify understand the cause for that exception, it is deprecated to omit the class name when calling `ORMInvalidArgumentException::invalidIdentifierBindingEntity()`. ## Deprecate `Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper` Using a console helper to provide the ORM's console commands with one or multiple entity managers had been deprecated with 2.9 already. This leaves The `EntityManagerHelper` class with no purpose which is why it is now deprecated too. Applications that still rely on the `em` console helper, can easily recreate that class in their own codebase. ## Deprecate custom repository classes that don't extend `EntityRepository` Although undocumented, it is currently possible to configure a custom repository class that implements `ObjectRepository` but does not extend the `EntityRepository` base class. This is now deprecated. Please extend `EntityRepository` instead. ## Deprecated more APIs related to entity namespace aliases ```diff -$config = $entityManager->getConfiguration(); -$config->addEntityNamespace('CMS', 'My\App\Cms'); +use My\App\Cms\CmsUser; -$entityManager->getRepository('CMS:CmsUser'); +$entityManager->getRepository(CmsUser::class); ``` ## Deprecate `AttributeDriver::getReader()` and `AnnotationDriver::getReader()` That method was inherited from the abstract `AnnotationDriver` class of `doctrine/persistence`, and does not seem to serve any purpose. ## Un-deprecate `Doctrine\ORM\Proxy\Proxy` Because no forward-compatible new proxy solution had been implemented yet, the current proxy mechanism is not considered deprecated anymore for the time being. This applies to the following interfaces/classes: * `Doctrine\ORM\Proxy\Proxy` * `Doctrine\ORM\Proxy\ProxyFactory` These methods have been un-deprecated: * `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()` * `Doctrine\ORM\Configuration::getProxyDir()` * `Doctrine\ORM\Configuration::getProxyNamespace()` Note that the `Doctrine\ORM\Proxy\Autoloader` remains deprecated and will be removed in 3.0. ## Deprecate helper methods from `AbstractCollectionPersister` The following protected methods of `Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister` are not in use anymore and will be removed. * `evictCollectionCache()` * `evictElementCache()` ## Deprecate `Doctrine\ORM\Query\TreeWalkerChainIterator` This class won't have a replacement. ## Deprecate `OnClearEventArgs::getEntityClass()` and `OnClearEventArgs::clearsAllEntities()` These methods will be removed in 3.0 along with the ability to partially clear the entity manager. ## Deprecate `Doctrine\ORM\Configuration::newDefaultAnnotationDriver` This functionality has been moved to the new `ORMSetup` class. Call `Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create a new annotation driver. ## Deprecate `Doctrine\ORM\Tools\Setup` In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which accepted a Doctrine Cache instance in each method has been deprecated. The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6 cache instead. ## Deprecate `Doctrine\ORM\Cache\MultiGetRegion` The interface will be merged with `Doctrine\ORM\Cache\Region` in 3.0. # Upgrade to 2.11 ## Rename `AbstractIdGenerator::generate()` to `generateId()` Implementations of `AbstractIdGenerator` have to override the method `generateId()` without calling the parent implementation. Not doing so is deprecated. Calling `generate()` on any `AbstractIdGenerator` implementation is deprecated. ## PSR-6-based second level cache The second level cache has been reworked to consume a PSR-6 cache. Using a Doctrine Cache instance is deprecated. * `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as second argument now. * `DefaultMultiGetRegion`: This class is deprecated in favor of `DefaultRegion`. * `DefaultRegion`: * The constructor expects a PSR-6 cache item pool as second argument now. * The protected `$cache` property is deprecated. * The properties `$name` and `$lifetime` as well as the constant `REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are flagged as `@internal` now. They all will become `private` in 3.0. * The method `getCache()` is deprecated without replacement. ## Deprecated: `Doctrine\ORM\Mapping\Driver\PHPDriver` Use `StaticPHPDriver` instead when you want to programmatically configure entity metadata. You can convert mappings with the `orm:convert-mapping` command or more simply in this case, `include` the metadata file from the `loadMetadata` static method used by the `StaticPHPDriver`. ## Deprecated: `Setup::registerAutoloadDirectory()` Use Composer's autoloader instead. ## Deprecated: `AbstractHydrator::hydrateRow()` Following the deprecation of the method `AbstractHydrator::iterate()`, the method `hydrateRow()` has been deprecated as well. ## Deprecate cache settings inspection Doctrine does not provide its own cache implementation anymore and relies on the PSR-6 standard instead. As a consequence, we cannot determine anymore whether a given cache adapter is suitable for a production environment. Because of that, functionality that aims to do so has been deprecated: * `Configuration::ensureProductionSettings()` * the `orm:ensure-production-settings` console command # Upgrade to 2.10 ## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes When calling the following methods, you are now supposed to use the result of `spl_object_id()`, and not `spl_object_hash()`: - `UnitOfWork::clearEntityChangeSet()` - `UnitOfWork::setOriginalEntityProperty()` ## BC Break: Removed `TABLE` id generator strategy The implementation was unfinished for 14 years. It is now deprecated to rely on: - `Doctrine\ORM\Id\TableGenerator`; - `Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_TABLE`; - `Doctrine\ORM\Mapping\ClassMetadata::$tableGeneratorDefinition`; - or `Doctrine\ORM\Mapping\ClassMetadata::isIdGeneratorTable()`. ## New method `Doctrine\ORM\EntityManagerInterface#wrapInTransaction($func)` Works the same as `Doctrine\ORM\EntityManagerInterface#transactional()` but returns any value returned from `$func` closure rather than just _non-empty value returned from the closure or true_. Because of BC policy, the method does not exist on the interface yet. This is the example of safe usage: ```php function foo(EntityManagerInterface $entityManager, callable $func) { if (method_exists($entityManager, 'wrapInTransaction')) { return $entityManager->wrapInTransaction($func); } return $entityManager->transactional($func); } ``` `Doctrine\ORM\EntityManagerInterface#transactional()` has been deprecated. ## Minor BC BREAK: some exception methods have been removed The following methods were not in use and are very unlikely to be used by downstream packages or applications, and were consequently removed: - `ORMException::entityMissingForeignAssignedId` - `ORMException::entityMissingAssignedIdForField` - `ORMException::invalidFlushMode` ## Deprecated: database-side UUID generation [DB-generated UUIDs are deprecated as of `doctrine/dbal` 2.8][DBAL deprecation]. As a consequence, using the `UUID` strategy for generating identifiers is deprecated as well. Furthermore, relying on the following classes and methods is deprecated: - `Doctrine\ORM\Id\UuidGenerator` - `Doctrine\ORM\Mapping\ClassMetadataInfo::isIdentifierUuid()` [DBAL deprecation]: https://github.com/doctrine/dbal/pull/3212 ## Minor BC BREAK: Custom hydrators and `toIterable()` The type declaration of the `$stmt` parameter of `AbstractHydrator::toIterable()` has been removed. This change might break custom hydrator implementations that override this very method. Overriding this method is not recommended, which is why the method is documented as `@final` now. ```diff - public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable + public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable ``` ## Deprecated: Entity Namespace Aliases Entity namespace aliases are deprecated, use the magic ::class constant to abbreviate full class names in EntityManager, EntityRepository and DQL. ```diff - $entityManager->find('MyBundle:User', $id); + $entityManager->find(User::class, $id); ``` # Upgrade to 2.9 ## Minor BC BREAK: Setup tool needs cache implementation With the deprecation of doctrine/cache, the setup tool might no longer work as expected without a different cache implementation. To work around this: * Install symfony/cache: `composer require symfony/cache`. This will keep previous behaviour without any changes * Instantiate caches yourself: to use a different cache implementation, pass a cache instance when calling any configuration factory in the setup tool: ```diff - $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir); + $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($anyPsr6Implementation); + $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache); ``` * As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`. Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache 1.11. ## Deprecated: doctrine/cache for metadata caching The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use `Doctrine\ORM\Configuration#setMetadataCache()` with any PSR-6 cache adapter instead. ## Removed: flushing metadata cache To support PSR-6 caches, the `--flush` option for the `orm:clear-cache:metadata` command is ignored. Metadata cache is now always cleared regardless of the cache adapter being used. # Upgrade to 2.8 ## Minor BC BREAK: Failed commit now throw OptimisticLockException Method `Doctrine\ORM\UnitOfWork#commit()` can throw an OptimisticLockException when a commit silently fails and returns false since `Doctrine\DBAL\Connection#commit()` signature changed from returning void to boolean ## Deprecated: `Doctrine\ORM\AbstractQuery#iterate()` The method `Doctrine\ORM\AbstractQuery#iterate()` is deprecated in favor of `Doctrine\ORM\AbstractQuery#toIterable()`. Note that `toIterable()` yields results of the query, unlike `iterate()` which yielded each result wrapped into an array. # Upgrade to 2.7 ## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache (depending on passed flag) was split into two. ## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to perform the pagination with join collections when max results isn't set in the query. ## Minor BC BREAK: tables filtered with `schema_filter` are no longer created When generating schema diffs, if a source table is filtered out by a `schema_filter` expression, then a `CREATE TABLE` was always generated, even if the table already existed. This has been changed in this release and the table will no longer be created. ## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy` In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers (e.g.: `base64Encoded` was wrongly converted to `base64encoded` instead of `base64_encoded`). In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This argument will be removed in 3.0 and the default behavior will be the fixed one. ## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()` Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()` and `disableResultCache()`. It will be removed in 3.0. ## Deprecated code generators and related console commands These console commands have been deprecated: * `orm:convert-mapping` * `orm:generate:entities` * `orm:generate-repositories` These classes have been deprecated: * `Doctrine\ORM\Tools\EntityGenerator` * `Doctrine\ORM\Tools\EntityRepositoryGenerator` Whole Doctrine\ORM\Tools\Export namespace with all its members have been deprecated as well. ## Deprecated `Doctrine\ORM\Proxy\Proxy` marker interface Proxy objects in Doctrine ORM 3.0 will no longer implement `Doctrine\ORM\Proxy\Proxy` nor `Doctrine\Persistence\Proxy`: instead, they implement `ProxyManager\Proxy\GhostObjectInterface`. These related classes have been deprecated: * `Doctrine\ORM\Proxy\ProxyFactory` * `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead These methods have been deprecated: * `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()` * `Doctrine\ORM\Configuration#getProxyDir()` * `Doctrine\ORM\Configuration#getProxyNamespace()` ## Deprecated `Doctrine\ORM\Version` The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0: please refrain from checking the ORM version at runtime or use Composer's [runtime API](https://getcomposer.org/doc/07-runtime.md#knowing-whether-package-x-is-installed-in-version-y). ## Deprecated `EntityManager#merge()` method Merge semantics was a poor fit for the PHP "share-nothing" architecture. In addition to that, merging caused multiple issues with data integrity in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios. The following API methods were therefore deprecated: * `EntityManager#merge()` * `UnitOfWork#merge()` An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging semantics should be part of the business domain rather than the persistence domain of an application. If your application relies heavily on CRUD-alike interactions and/or `PATCH` restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer). ## Extending `EntityManager` is deprecated Final keyword will be added to the `EntityManager::class` in Doctrine ORM 3.0 in order to ensure that EntityManager is not used as valid extension point. Valid extension point should be EntityManagerInterface. ## Deprecated `EntityManager#clear($entityName)` If your code relies on clearing a single entity type via `EntityManager#clear($entityName)`, the signature has been changed to `EntityManager#clear()`. The main reason is that partial clears caused multiple issues with data integrity in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios. ## Deprecated `EntityManager#flush($entity)` and `EntityManager#flush($entities)` If your code relies on single entity flushing optimisations via `EntityManager#flush($entity)`, the signature has been changed to `EntityManager#flush()`. Said API was affected by multiple data integrity bugs due to the fact that change tracking was being restricted upon a subset of the managed entities. The ORM cannot support committing subsets of the managed entities while also guaranteeing data integrity, therefore this utility was removed. The `flush()` semantics will remain the same, but the change tracking will be performed on all entities managed by the unit of work, and not just on the provided `$entity` or `$entities`, as the parameter is now completely ignored. The same applies to `UnitOfWork#commit($entity)`, which will simply be `UnitOfWork#commit()`. If you would still like to perform batching operations over small `UnitOfWork` instances, it is suggested to follow these paths instead: * eagerly use `EntityManager#clear()` in conjunction with a specific second level cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/second-level-cache.html) * use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/change-tracking-policies.html) ## Deprecated `YAML` mapping drivers. If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** change to annotation or XML drivers instead. ## Deprecated: `Doctrine\ORM\EntityManagerInterface#copy()` Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is deprecated. It will be removed in 3.0. # Upgrade to 2.6 ## Added `Doctrine\ORM\EntityRepository::count()` method `Doctrine\ORM\EntityRepository::count()` has been added. This new method has different signature than `Countable::count()` (required parameter) and therefore are not compatible. If your repository implemented the `Countable` interface, you will have to use `$repository->count([])` instead and not implement `Countable` interface anymore. ## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final Since it's just an utilitarian class and should not be inherited. ## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()` Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()` now has a required parameter `$pathExpr`. ## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()` Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because the distinction between internal function and user defined DQL was removed. [#6500](https://github.com/doctrine/orm/pull/6500) ## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()` Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was removed because of the choice to allow users to overwrite internal functions, ie `AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500) ## PHP 7.1 is now required Doctrine 2.6 now requires PHP 7.1 or newer. As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed: - APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc). - Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache). - XCache support was dropped as it doesn't work with PHP 7. # Upgrade to 2.5 ## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600). ## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()` As `$className` parameter was not used in the method, it was safely removed. ## Minor BC BREAK: query cache key time is now a float As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value instead of an integer in order to have more precision and also to be consistent with the `TimestampCacheEntry#time`. ## Minor BC BREAK: discriminator map must now include all non-transient classes It is now required that you declare the root of an inheritance in the discriminator map. When declaring an inheritance map, it was previously possible to skip the root of the inheritance in the discriminator map. This was actually a validation mistake by Doctrine2 and led to problems when trying to persist instances of that class. If you don't plan to persist instances some classes in your inheritance, then either: - make those classes `abstract` - map those classes as `MappedSuperclass` ## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require an ``EntityManagerInterface`` instead. If you are extending any of the following classes, then you need to check following signatures: - ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)`` - ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)`` ## Minor BC BREAK: Custom Hydrators API change As of 2.5, `AbstractHydrator` does not enforce the usage of cache as part of API, and now provides you a clean API for column information through the method `hydrateColumnInfo($column)`. Cache variable being passed around by reference is no longer needed since Hydrators are per query instantiated since Doctrine 2.4. ## Minor BC BREAK: Entity based ``EntityManager#clear()`` calls follow cascade detach Whenever ``EntityManager#clear()`` method gets called with a given entity class name, until 2.4, it was only detaching the specific requested entity. As of 2.5, ``EntityManager`` will follow configured cascades, providing a better memory management since associations will be garbage collected, optimizing resources consumption on long running jobs. ## BC BREAK: NamingStrategy interface changes 1. A new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)`` This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you now also need to implement this new method. 2. A change to method ``joinColumnName()`` to include the $className ## Updates on entities scheduled for deletion are no longer processed In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would produce an UPDATE statement to be executed right before the DELETE statement. The entity in question was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate`` listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both ``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset calculation logic is optimized away. ## Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures A misconception concerning default lock mode values in method signatures lead to unexpected behaviour in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)`` table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED instead of the default READ COMMITTED transaction isolation level. Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``: - ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()`` - ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()`` - ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()`` - ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()`` - ``Doctrine\ORM\EntityManager#find()`` - ``Doctrine\ORM\EntityRepository#find()`` - ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()`` - ``Doctrine\ORM\Persisters\BasicEntityPersister#load()`` - ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()`` - ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()`` - ``Doctrine\ORM\Persisters\EntityPersister#load()`` - ``Doctrine\ORM\Persisters\EntityPersister#refresh()`` - ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()`` You should update signatures for these methods if you have subclassed one of the above classes. Please also check the calling code of these methods in your application and update if necessary. **Note:** This in fact is really a minor BC BREAK and should not have any affect on database vendors other than SQL Server because it is the only one that supports and therefore cares about ``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM. ## Minor BC BREAK: `__clone` method not called anymore when entities are instantiated via metadata API As of PHP 5.6, instantiation of new entities is deferred to the [`doctrine/instantiator`](https://github.com/doctrine/instantiator) library, which will avoid calling `__clone` or any public API on instantiated objects. ## BC BREAK: `Doctrine\ORM\Repository\DefaultRepositoryFactory` is now `final` Please implement the `Doctrine\ORM\Repository\RepositoryFactory` interface instead of extending the `Doctrine\ORM\Repository\DefaultRepositoryFactory`. ## BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query: SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true Previously, your result would be similar to this: array( 0=>array( 0=>{UserDTO object}, 1=>{AddressDTO object}, 2=>{u.id scalar}, 3=>{u.name scalar}, 4=>{a.street scalar}, 5=>{a.postalCode scalar}, 'addressId'=>{a.id scalar}, ), ... ) From now on, the resultset will look like this: array( 0=>array( 'user'=>{UserDTO object}, 'address'=>{AddressDTO object}, 'addressId'=>{a.id scalar} ), ... ) ## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder() # Upgrade to 2.4 ## BC BREAK: Compatibility Bugfix in PersistentCollection#matching() In Doctrine 2.3 it was possible to use the new ``matching($criteria)`` functionality by adding constraints for assocations based on ID: Criteria::expr()->eq('association', $assocation->getId()); This functionality does not work on InMemory collections however, because in memory criteria compares object values based on reference. As of 2.4 the above code will throw an exception. You need to change offending code to pass the ``$assocation`` reference directly: Criteria::expr()->eq('association', $assocation); ## Composer is now the default autoloader The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated. Support for GIT submodules is removed. ## OnFlush and PostFlush event always called Before 2.4 the postFlush and onFlush events were only called when there were actually entities that changed. Now these events are called no matter if there are entities in the UoW or changes are found. ## Parenthesis are now considered in arithmetic expression Before 2.4 parenthesis are not considered in arithmetic primary expression. That's conceptually wrong, since it might result in wrong values. For example: The DQL: SELECT 100 / ( 2 * 2 ) FROM MyEntity Before 2.4 it generates the SQL: SELECT 100 / 2 * 2 FROM my_entity Now parenthesis are considered, the previous DQL will generate: SELECT 100 / (2 * 2) FROM my_entity # Upgrade to 2.3 ## Auto Discriminator Map breaks userland implementations with Listener The new feature to detect discriminator maps automatically when none are provided breaks userland implementations doing this with a listener in ``loadClassMetadata`` event. ## EntityManager#find() not calls EntityRepository#find() anymore Previous to 2.3, calling ``EntityManager#find()`` would be delegated to ``EntityRepository#find()``. This has lead to some unexpected behavior in the core of Doctrine when people have overwritten the find method in their repositories. That is why this behavior has been reversed in 2.3, and ``EntityRepository#find()`` calls ``EntityManager#find()`` instead. ## EntityGenerator add*() method generation When generating an add*() method for a collection the EntityGenerator will now not use the Type-Hint to get the singular for the collection name, but use the field-name and strip a trailing "s" character if there is one. ## Merge copies non persisted properties too When merging an entity in UoW not only mapped properties are copied, but also others. ## Query, QueryBuilder and NativeQuery parameters *BC break* From now on, parameters in queries is an ArrayCollection instead of a simple array. This affects heavily the usage of setParameters(), because it will not append anymore parameters to query, but will actually override the already defined ones. Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will receive an instance of Query\Parameter, which contains the methods "getName", "getValue" and "getType". Parameters are also only converted to when necessary, and not when they are set. Also, related functions were affected: * execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance * setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance * getParameters() now returns ArrayCollection instead of array * getParameter($key) now returns Parameter instance instead of parameter value ## Query TreeWalker method renamed Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker, you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin. ## New methods in TreeWalker interface *BC break* Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the above you must implement these new methods. ## Metadata Drivers Metadata drivers have been rewritten to reuse code from `Doctrine\Persistence`. Anyone who is using the `Doctrine\ORM\Mapping\Driver\Driver` interface should instead refer to `Doctrine\Persistence\Mapping\Driver\MappingDriver`. Same applies to `Doctrine\ORM\Mapping\Driver\AbstractFileDriver`: you should now refer to `Doctrine\Persistence\Mapping\Driver\FileDriver`. Also, following mapping drivers have been deprecated, please use their replacements in Doctrine\Common as listed: * `Doctrine\ORM\Mapping\Driver\DriverChain` => `Doctrine\Persistence\Mapping\Driver\MappingDriverChain` * `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Persistence\Mapping\Driver\PHPDriver` * `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Persistence\Mapping\Driver\StaticPHPDriver` # Upgrade to 2.2 ## ResultCache implementation rewritten The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery anymore. This means that for result cached queries the hydration will now always be performed again, regardless of the hydration mode. Affected areas are: 1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore. 2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result. The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` instance with access to result cache driver, lifetime and cache key. ## EntityManager#getPartialReference() creates read-only entity Entities returned from EntityManager#getPartialReference() are now marked as read-only if they haven't been in the identity map before. This means objects of this kind never lead to changes in the UnitOfWork. ## Fields omitted in a partial DQL query or a native query are never updated Fields of an entity that are not returned from a partial DQL Query or native SQL query will never be updated through an UPDATE statement. ## Removed support for onUpdate in @JoinColumn The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. ## Changes in Annotation Handling There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`: // Register the ORM Annotations in the AnnotationRegistry AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); $reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader(); $reader->addNamespace('Doctrine\ORM\Mapping'); $reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache()); $driver = new AnnotationDriver($reader, (array)$paths); $config->setMetadataDriverImpl($driver); ## Scalar mappings can now be omitted from DQL result You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore. Example: SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10 Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user ## Map entities as scalars in DQL result When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance. You are now allowed to alias this, providing more flexibility for you code. Example: SELECT u AS user FROM User u Will now return a collection of arrays with index "user" pointing to the User object instance. ## Performance optimizations Thousands of lines were completely reviewed and optimized for best performance. Removed redundancy and improved code readability made now internal Doctrine code easier to understand. Also, Doctrine 2.2 now is around 10-15% faster than 2.1. ## EntityManager#find(null) Previously EntityManager#find(null) returned null. It now throws an exception. # Upgrade to 2.1 ## Interface for EntityRepository The EntityRepository now has an interface Doctrine\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface. ## AnnotationReader changes The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way: // new call to the AnnotationRegistry \Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); // new code necessary starting here $reader->setIgnoreNotImportedAnnotations(true); $reader->setEnableParsePhpImports(false); $reader = new \Doctrine\Common\Annotations\CachedReader( new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() ); This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory. # Update from 2.0-BETA3 to 2.0-BETA4 ## XML Driver <change-tracking-policy /> element demoted to attribute We changed how the XML Driver allows to define the change-tracking-policy. The working case is now: <entity change-tracking-policy="DEFERRED_IMPLICT" /> # Update from 2.0-BETA2 to 2.0-BETA3 ## Serialization of Uninitialized Proxies As of Beta3 you can now serialize uninitialized proxies, an exception will only be thrown when trying to access methods on the unserialized proxy as long as it has not been re-attached to the EntityManager using `EntityManager#merge()`. See this example: $proxy = $em->getReference('User', 1); $serializedProxy = serialize($proxy); $detachedProxy = unserialized($serializedProxy); echo $em->contains($detachedProxy); // FALSE try { $detachedProxy->getId(); // uninitialized detached proxy } catch(Exception $e) { } $attachedProxy = $em->merge($detachedProxy); echo $attackedProxy->getId(); // works! ## Changed SQL implementation of Postgres and Oracle DateTime types The DBAL Type "datetime" included the Timezone Offset in both Postgres and Oracle. As of this version they are now generated without Timezone (TIMESTAMP WITHOUT TIME ZONE instead of TIMESTAMP WITH TIME ZONE). See [this comment to Ticket DBAL-22](http://www.doctrine-project.org/jira/browse/DBAL-22?focusedCommentId=13396&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_13396) for more details as well as migration issues for PostgreSQL and Oracle. Both Postgres and Oracle will throw Exceptions during hydration of Objects with "DateTime" fields unless migration steps are taken! ## Removed multi-dot/deep-path expressions in DQL The support for implicit joins in DQL through the multi-dot/Deep Path Expressions was dropped. For example: SELECT u FROM User u WHERE u.group.name = ?1 See the "u.group.id" here is using multi dots (deep expression) to walk through the graph of objects and properties. Internally the DQL parser would rewrite these queries to: SELECT u FROM User u JOIN u.group g WHERE g.name = ?1 This explicit notation will be the only supported notation as of now. The internal handling of multi-dots in the DQL Parser was very complex, error prone in edge cases and required special treatment for several features we added. Additionally it had edge cases that could not be solved without making the DQL Parser even much more complex. For this reason we will drop the support for the deep path expressions to increase maintainability and overall performance of the DQL parsing process. This will benefit any DQL query being parsed, even those not using deep path expressions. Note that the generated SQL of both notations is exactly the same! You don't loose anything through this. ## Default Allocation Size for Sequences The default allocation size for sequences has been changed from 10 to 1. This step was made to not cause confusion with users and also because it is partly some kind of premature optimization. # Update from 2.0-BETA1 to 2.0-BETA2 There are no backwards incompatible changes in this release. # Upgrade from 2.0-ALPHA4 to 2.0-BETA1 ## EntityRepository deprecates access to protected variables Instead of accessing protected variables for the EntityManager in a custom EntityRepository it is now required to use the getter methods for all the three instance variables: * `$this->_em` now accessible through `$this->getEntityManager()` * `$this->_class` now accessible through `$this->getClassMetadata()` * `$this->_entityName` now accessible through `$this->getEntityName()` Important: For Beta 2 the protected visibility of these three properties will be changed to private! ## Console migrated to Symfony Console The Doctrine CLI has been replaced by Symfony Console Configuration Instead of having to specify: [php] $cliConfig = new CliConfiguration(); $cliConfig->setAttribute('em', $entityManager); You now have to configure the script like: [php] $helperSet = new \Symfony\Components\Console\Helper\HelperSet(array( 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) )); ## Console: No need for Mapping Paths anymore In previous versions you had to specify the --from and --from-path options to show where your mapping paths are from the console. However this information is already known from the Mapping Driver configuration, so the requirement for this options were dropped. Instead for each console command all the entities are loaded and to restrict the operation to one or more sub-groups you can use the --filter flag. ## AnnotationDriver is not a default mapping driver anymore In conjunction with the recent changes to Console we realized that the annotations driver being a default metadata driver lead to lots of glue code in the console components to detect where entities lie and how to load them for batch updates like SchemaTool and other commands. However the annotations driver being a default driver does not really help that much anyways. Therefore we decided to break backwards compatibility in this issue and drop the support for Annotations as Default Driver and require our users to specify the driver explicitly (which allows us to ask for the path to all entities). If you are using the annotations metadata driver as default driver, you have to add the following lines to your bootstrap code: $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__."/Entities")); $config->setMetadataDriverImpl($driverImpl); You have to specify the path to your entities as either string of a single path or array of multiple paths to your entities. This information will be used by all console commands to access all entities. Xml and Yaml Drivers work as before! ## New inversedBy attribute It is now *mandatory* that the owning side of a bidirectional association specifies the 'inversedBy' attribute that points to the name of the field on the inverse side that completes the association. Example: [php] // BEFORE (ALPHA4 AND EARLIER) class User { //... /** @OneToOne(targetEntity="Address", mappedBy="user") */ private $address; //... } class Address { //... /** @OneToOne(targetEntity="User") */ private $user; //... } // SINCE BETA1 // User class DOES NOT CHANGE class Address { //... /** @OneToOne(targetEntity="User", inversedBy="address") */ private $user; //... } Thus, the inversedBy attribute is the counterpart to the mappedBy attribute. This change was necessary to enable some simplifications and further performance improvements. We apologize for the inconvenience. ## Default Property for Field Mappings The "default" option for database column defaults has been removed. If desired, database column defaults can be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents). Prefer PHP default values, if possible. ## Selecting Partial Objects Querying for partial objects now has a new syntax. The old syntax to query for partial objects now has a different meaning. This is best illustrated by an example. If you previously had a DQL query like this: [sql] SELECT u.id, u.name FROM User u Since BETA1, simple state field path expressions in the select clause are used to select object fields as plain scalar values (something that was not possible before). To achieve the same result as previously (that is, a partial object with only id and name populated) you need to use the following, explicit syntax: [sql] SELECT PARTIAL u.{id,name} FROM User u ## XML Mapping Driver The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e. NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED ## YAML Mapping Driver The way to specify lifecycle callbacks in YAML Mapping driver was changed to allow for multiple callbacks per event. The Old syntax ways: [yaml] lifecycleCallbacks: doStuffOnPrePersist: prePersist doStuffOnPostPersist: postPersist The new syntax is: [yaml] lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] ## PreUpdate Event Listeners Event Listeners listening to the 'preUpdate' event can only affect the primitive values of entity changesets by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate listener method. Any changes to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic performance benefits for the preUpdate event. ## Collection API The Collection interface in the Common package has been updated with some missing methods that were present only on the default implementation, ArrayCollection. Custom collection implementations need to be updated to adhere to the updated interface. # Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 ## CLI Controller changes CLI main object changed its name and namespace. Renamed from Doctrine\ORM\Tools\Cli to Doctrine\Common\Cli\CliController. Doctrine\Common\Cli\CliController now only deals with namespaces. Ready to go, Core, Dbal and Orm are available and you can subscribe new tasks by retrieving the namespace and including new task. Example: [php] $cli->getNamespace('Core')->addTask('my-example', '\MyProject\Tools\Cli\Tasks\MyExampleTask'); ## CLI Tasks documentation Tasks have implemented a new way to build documentation. Although it is still possible to define the help manually by extending the basicHelp and extendedHelp, they are now optional. With new required method AbstractTask::buildDocumentation, its implementation defines the TaskDocumentation instance (accessible through AbstractTask::getDocumentation()), basicHelp and extendedHelp are now not necessary to be implemented. ## Changes in Method Signatures * A bunch of Methods on both Doctrine\DBAL\Platforms\AbstractPlatform and Doctrine\DBAL\Schema\AbstractSchemaManager have changed quite significantly by adopting the new Schema instance objects. ## Renamed Methods * Doctrine\ORM\AbstractQuery::setExpireResultCache() -> expireResultCache() * Doctrine\ORM\Query::setExpireQueryCache() -> expireQueryCache() ## SchemaTool Changes * "doctrine schema-tool --drop" now always drops the complete database instead of only those tables defined by the current database model. The previous method had problems when foreign keys of orphaned tables pointed to tables that were scheduled for deletion. * Use "doctrine schema-tool --update" to get a save incremental update for your database schema without deleting any unused tables, sequences or foreign keys. * Use "doctrine schema-tool --complete-update" to do a full incremental update of your schema. # Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3 This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you to upgrade your projects to use this version. ## CLI Changes The $args variable used in the cli-config.php for configuring the Doctrine CLI has been renamed to $globalArguments. ## Proxy class changes You are now required to make supply some minimalist configuration with regards to proxy objects. That involves 2 new configuration options. First, the directory where generated proxy classes should be placed needs to be specified. Secondly, you need to configure the namespace used for proxy classes. The following snippet shows an example: [php] // step 1: configure directory for proxy classes // $config instanceof Doctrine\ORM\Configuration $config->setProxyDir('/path/to/myproject/lib/MyProject/Generated/Proxies'); $config->setProxyNamespace('MyProject\Generated\Proxies'); Note that proxy classes behave exactly like any other classes when it comes to class loading. Therefore you need to make sure the proxy classes can be loaded by some class loader. If you place the generated proxy classes in a namespace and directory under your projects class files, like in the example above, it would be sufficient to register the MyProject namespace on a class loader. Since the proxy classes are contained in that namespace and adhere to the standards for class loading, no additional work is required. Generating the proxy classes into a namespace within your class library is the recommended setup. Entities with initialized proxy objects can now be serialized and unserialized properly from within the same application. For more details refer to the Configuration section of the manual. ## Removed allowPartialObjects configuration option The allowPartialObjects configuration option together with the `Configuration#getAllowPartialObjects` and `Configuration#setAllowPartialObjects` methods have been removed. The new behavior is as if the option were set to FALSE all the time, basically disallowing partial objects globally. However, you can still use the `Query::HINT_FORCE_PARTIAL_LOAD` query hint to force a query to return partial objects for optimization purposes. ## Renamed Methods * Doctrine\ORM\Configuration#getCacheDir() to getProxyDir() * Doctrine\ORM\Configuration#setCacheDir($dir) to setProxyDir($dir) orm/composer.json 0000644 00000005706 15120025736 0010074 0 ustar 00 { "name": "doctrine/orm", "type": "library", "description": "Object-Relational-Mapper for PHP", "keywords": ["orm", "database"], "homepage": "https://www.doctrine-project.org/projects/orm.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, {"name": "Marco Pivetta", "email": "ocramius@gmail.com"} ], "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true }, "require": { "php": "^7.1 || ^8.0", "composer-runtime-api": "^2", "ext-ctype": "*", "doctrine/cache": "^1.12.1 || ^2.1.1", "doctrine/collections": "^1.5 || ^2.1", "doctrine/common": "^3.0.3", "doctrine/dbal": "^2.13.1 || ^3.2", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", "doctrine/lexer": "^2", "doctrine/persistence": "^2.4 || ^3", "psr/cache": "^1 || ^2 || ^3", "symfony/console": "^4.2 || ^5.0 || ^6.0", "symfony/polyfill-php72": "^1.23", "symfony/polyfill-php80": "^1.16" }, "require-dev": { "doctrine/annotations": "^1.13 || ^2", "doctrine/coding-standard": "^9.0.2 || ^12.0", "phpbench/phpbench": "^0.16.10 || ^1.0", "phpstan/phpstan": "~1.4.10 || 1.10.18", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", "vimeo/psalm": "4.30.0 || 5.12.0" }, "conflict": { "doctrine/annotations": "<1.13 || >= 3.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" }, "autoload": { "psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests", "Doctrine\\StaticAnalysis\\": "tests/Doctrine/StaticAnalysis", "Doctrine\\Performance\\": "tests/Doctrine/Performance" } }, "bin": ["bin/doctrine"], "archive": { "exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"] } } orm/doctrine-mapping.xsd 0000644 00000072415 15120025736 0011333 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:orm="http://doctrine-project.org/schemas/orm/doctrine-mapping" elementFormDefault="qualified"> <xs:annotation> <xs:documentation><![CDATA[ This is the XML Schema for the object/relational mapping file used by the Doctrine ORM. ]]></xs:documentation> </xs:annotation> <xs:element name="doctrine-mapping"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="embeddable" type="orm:embeddable" minOccurs="0" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> </xs:element> <xs:complexType name="emptyType"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="cascade-type"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/> <xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/> <xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/> <xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/> <xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/> <xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:simpleType name="lifecycle-callback-type"> <xs:restriction base="xs:token"> <xs:enumeration value="prePersist"/> <xs:enumeration value="postPersist"/> <xs:enumeration value="preUpdate"/> <xs:enumeration value="postUpdate"/> <xs:enumeration value="preRemove"/> <xs:enumeration value="postRemove"/> <xs:enumeration value="postLoad"/> <xs:enumeration value="preFlush"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="cache-usage-type"> <xs:restriction base="xs:token"> <xs:enumeration value="READ_ONLY"/> <xs:enumeration value="READ_WRITE"/> <xs:enumeration value="NONSTRICT_READ_WRITE"/> </xs:restriction> </xs:simpleType> <xs:complexType name="lifecycle-callback"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="type" type="orm:lifecycle-callback-type" use="required" /> <xs:attribute name="method" type="xs:NMTOKEN" use="required" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="lifecycle-callbacks"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="named-query"> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="query" type="xs:string" use="required" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="named-queries"> <xs:sequence> <xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:sequence> </xs:complexType> <xs:complexType name="named-native-query"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="query" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="result-class" type="orm:fqcn" /> <xs:attribute name="result-set-mapping" type="xs:string" /> </xs:complexType> <xs:complexType name="named-native-queries"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="named-native-query" type="orm:named-native-query" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> </xs:complexType> <xs:complexType name="entity-listener"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="class" type="orm:fqcn"/> </xs:complexType> <xs:complexType name="entity-listeners"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="entity-listener" type="orm:entity-listener" minOccurs="1" maxOccurs="unbounded" /> </xs:choice> </xs:complexType> <xs:complexType name="column-result"> <xs:attribute name="name" type="xs:string" use="required" /> </xs:complexType> <xs:complexType name="field-result"> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="column" type="xs:string" /> </xs:complexType> <xs:complexType name="entity-result"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" /> </xs:choice> <xs:attribute name="entity-class" type="orm:fqcn" use="required" /> <xs:attribute name="discriminator-column" type="xs:string" use="optional" /> </xs:complexType> <xs:complexType name="sql-result-set-mapping"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="entity-result" type="orm:entity-result"/> <xs:element name="column-result" type="orm:column-result"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:string" use="required" /> </xs:complexType> <xs:complexType name="sql-result-set-mappings"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="sql-result-set-mapping" type="orm:sql-result-set-mapping" minOccurs="1" maxOccurs="unbounded" /> </xs:choice> </xs:complexType> <xs:complexType name="cache"> <xs:attribute name="usage" type="orm:cache-usage-type" /> <xs:attribute name="region" type="xs:string" /> </xs:complexType> <xs:complexType name="entity"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:element name="indexes" type="orm:indexes" minOccurs="0"/> <xs:element name="unique-constraints" type="orm:unique-constraints" minOccurs="0"/> <xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/> <xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/> <xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" /> <xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" /> <xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" /> <xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" /> <xs:element name="sql-result-set-mappings" type="orm:sql-result-set-mappings" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="embedded" type="orm:embedded" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="table" type="orm:tablename" /> <xs:attribute name="schema" type="xs:NMTOKEN" /> <xs:attribute name="repository-class" type="orm:fqcn"/> <xs:attribute name="inheritance-type" type="orm:inheritance-type"/> <xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" /> <xs:attribute name="read-only" type="xs:boolean" default="false" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:simpleType name="tablename" id="tablename"> <xs:restriction base="xs:token"> <xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern"> </xs:pattern> </xs:restriction> </xs:simpleType> <xs:complexType name="option" mixed="true"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="option" type="orm:option"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required"/> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="options"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="option" type="orm:option" minOccurs="0" maxOccurs="unbounded"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="mapped-superclass" > <xs:complexContent> <xs:extension base="orm:entity"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="embeddable"> <xs:complexContent> <xs:extension base="orm:entity"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> </xs:extension> </xs:complexContent> </xs:complexType> <xs:simpleType name="change-tracking-policy"> <xs:restriction base="xs:token"> <xs:enumeration value="DEFERRED_IMPLICIT"/> <xs:enumeration value="DEFERRED_EXPLICIT"/> <xs:enumeration value="NOTIFY"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="inheritance-type"> <xs:restriction base="xs:token"> <xs:enumeration value="SINGLE_TABLE"/> <xs:enumeration value="JOINED"/> <xs:enumeration value="TABLE_PER_CLASS"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="generator-strategy"> <xs:restriction base="xs:token"> <xs:enumeration value="NONE"/> <xs:enumeration value="SEQUENCE"/> <xs:enumeration value="IDENTITY"/> <xs:enumeration value="AUTO"/> <xs:enumeration value="UUID"/> <xs:enumeration value="CUSTOM" /> </xs:restriction> </xs:simpleType> <xs:simpleType name="fk-action"> <xs:restriction base="xs:token"> <xs:enumeration value="CASCADE"/> <xs:enumeration value="RESTRICT"/> <xs:enumeration value="SET NULL"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="fetch-type"> <xs:restriction base="xs:token"> <xs:enumeration value="EAGER"/> <xs:enumeration value="LAZY"/> <xs:enumeration value="EXTRA_LAZY"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="generated-type"> <xs:restriction base="xs:token"> <xs:enumeration value="NEVER"/> <xs:enumeration value="INSERT"/> <xs:enumeration value="ALWAYS"/> </xs:restriction> </xs:simpleType> <xs:complexType name="field"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="type" type="orm:type" default="string" /> <xs:attribute name="column" type="orm:columntoken" /> <xs:attribute name="length" type="xs:NMTOKEN" /> <xs:attribute name="unique" type="xs:boolean" default="false" /> <xs:attribute name="nullable" type="xs:boolean" default="false" /> <xs:attribute name="insertable" type="xs:boolean" default="true" /> <xs:attribute name="updatable" type="xs:boolean" default="true" /> <xs:attribute name="generated" type="orm:generated-type" default="NEVER" /> <xs:attribute name="enum-type" type="xs:string" /> <xs:attribute name="version" type="xs:boolean" /> <xs:attribute name="column-definition" type="xs:string" /> <xs:attribute name="precision" type="xs:integer" use="optional" /> <xs:attribute name="scale" type="xs:integer" use="optional" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="embedded"> <xs:sequence> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:sequence> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="class" type="orm:fqcn" use="optional" /> <xs:attribute name="column-prefix" type="xs:string" use="optional" /> <xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" /> </xs:complexType> <xs:complexType name="discriminator-column"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="type" type="xs:NMTOKEN"/> <xs:attribute name="field-name" type="xs:NMTOKEN" /> <xs:attribute name="length" type="xs:NMTOKEN" /> <xs:attribute name="column-definition" type="xs:string" /> <xs:attribute name="enum-type" type="xs:string" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="unique-constraint"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="optional"/> <xs:attribute name="columns" type="xs:string" use="optional"/> <xs:attribute name="fields" type="xs:string" use="optional"/> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="unique-constraints"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="index"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="optional"/> <xs:attribute name="columns" type="xs:string" use="optional"/> <xs:attribute name="fields" type="xs:string" use="optional"/> <xs:attribute name="flags" type="xs:string" use="optional"/> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="indexes"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="discriminator-mapping"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="value" type="xs:NMTOKEN" use="required"/> <xs:attribute name="class" type="orm:fqcn" use="required"/> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="discriminator-map"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="generator"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="strategy" type="orm:generator-strategy" use="optional" default="AUTO" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="id"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="generator" type="orm:generator" minOccurs="0" /> <xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" /> <xs:element name="custom-id-generator" type="orm:custom-id-generator" minOccurs="0" maxOccurs="1" /> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="type" type="orm:type" /> <xs:attribute name="column" type="orm:columntoken" /> <xs:attribute name="length" type="xs:NMTOKEN" /> <xs:attribute name="association-key" type="xs:boolean" default="false" /> <xs:attribute name="column-definition" type="xs:string" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="sequence-generator"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="sequence-name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="allocation-size" type="xs:integer" use="optional" default="1" /> <xs:attribute name="initial-value" type="xs:integer" use="optional" default="1" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="custom-id-generator"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="class" type="orm:fqcn" use="required" /> </xs:complexType> <xs:simpleType name="fqcn" id="fqcn"> <xs:restriction base="xs:token"> <xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern"> </xs:pattern> </xs:restriction> </xs:simpleType> <xs:simpleType name="type" id="type"> <xs:restriction base="xs:token"> <xs:pattern value="([a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+)|(\c+)" id="type.class.pattern"> </xs:pattern> </xs:restriction> </xs:simpleType> <xs:complexType name="inverse-join-columns"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="join-column"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="optional" /> <xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" /> <xs:attribute name="unique" type="xs:boolean" default="false" /> <xs:attribute name="nullable" type="xs:boolean" default="true" /> <xs:attribute name="on-delete" type="orm:fk-action" /> <xs:attribute name="column-definition" type="xs:string" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="join-columns"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="join-table"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="join-columns" type="orm:join-columns" /> <xs:element name="inverse-join-columns" type="orm:join-columns" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="schema" type="xs:NMTOKEN" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="order-by"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="order-by-field" type="orm:order-by-field" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="order-by-field"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="direction" type="orm:order-by-direction" default="ASC" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:simpleType name="order-by-direction"> <xs:restriction base="xs:token"> <xs:enumeration value="ASC"/> <xs:enumeration value="DESC"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="columntoken" id="columntoken"> <xs:restriction base="xs:token"> <xs:pattern value="[-._:A-Za-z0-9`]+" id="columntoken.pattern"/> </xs:restriction> </xs:simpleType> <xs:complexType name="many-to-many"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/> <xs:element name="cascade" type="orm:cascade-type" minOccurs="0" /> <xs:element name="join-table" type="orm:join-table" minOccurs="0" /> <xs:element name="order-by" type="orm:order-by" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="target-entity" type="xs:string" use="required" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" /> <xs:attribute name="inversed-by" type="xs:NMTOKEN" /> <xs:attribute name="index-by" type="xs:NMTOKEN" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="one-to-many"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/> <xs:element name="cascade" type="orm:cascade-type" minOccurs="0" /> <xs:element name="order-by" type="orm:order-by" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="target-entity" type="xs:string" use="required" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" /> <xs:attribute name="index-by" type="xs:NMTOKEN" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="many-to-one"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/> <xs:element name="cascade" type="orm:cascade-type" minOccurs="0" /> <xs:choice minOccurs="0" maxOccurs="1"> <xs:element name="join-column" type="orm:join-column"/> <xs:element name="join-columns" type="orm:join-columns"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="target-entity" type="xs:string" /> <xs:attribute name="inversed-by" type="xs:NMTOKEN" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="one-to-one"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/> <xs:element name="cascade" type="orm:cascade-type" minOccurs="0" /> <xs:choice minOccurs="0" maxOccurs="1"> <xs:element name="join-column" type="orm:join-column"/> <xs:element name="join-columns" type="orm:join-columns"/> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="field" type="xs:NMTOKEN" use="required" /> <xs:attribute name="target-entity" type="xs:string" /> <xs:attribute name="mapped-by" type="xs:NMTOKEN" /> <xs:attribute name="inversed-by" type="xs:NMTOKEN" /> <xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" /> <xs:attribute name="orphan-removal" type="xs:boolean" default="false" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> <xs:complexType name="association-overrides"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="association-override" type="orm:association-override" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> </xs:complexType> <xs:complexType name="association-override"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="join-table" type="orm:join-table" minOccurs="0" /> <xs:element name="join-columns" type="orm:join-columns" minOccurs="0" /> <xs:element name="inversed-by" type="orm:inversed-by-override" minOccurs="0" maxOccurs="1" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> <xs:attribute name="fetch" type="orm:fetch-type" use="optional" /> </xs:complexType> <xs:complexType name="inversed-by-override"> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> </xs:complexType> <xs:complexType name="attribute-overrides"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="attribute-override" type="orm:attribute-override" minOccurs="1" maxOccurs="unbounded" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> </xs:complexType> <xs:complexType name="attribute-override"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="field" type="orm:attribute-override-field" minOccurs="1" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="name" type="xs:NMTOKEN" use="required" /> </xs:complexType> <xs:complexType name="attribute-override-field"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="options" type="orm:options" minOccurs="0" /> <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/> </xs:choice> <xs:attribute name="type" type="orm:type" default="string" /> <xs:attribute name="column" type="orm:columntoken" /> <xs:attribute name="length" type="xs:NMTOKEN" /> <xs:attribute name="unique" type="xs:boolean" default="false" /> <xs:attribute name="nullable" type="xs:boolean" default="false" /> <xs:attribute name="insertable" type="xs:boolean" default="true" /> <xs:attribute name="updateable" type="xs:boolean" default="true" /> <xs:attribute name="version" type="xs:boolean" /> <xs:attribute name="column-definition" type="xs:string" /> <xs:attribute name="precision" type="xs:integer" use="optional" /> <xs:attribute name="scale" type="xs:integer" use="optional" /> <xs:anyAttribute namespace="##other"/> </xs:complexType> </xs:schema> data-fixtures/src/Event/Listener/MongoDBReferenceListener.php 0000644 00000002525 15120025736 0020311 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Event\Listener; use Doctrine\Common\DataFixtures\ReferenceRepository; use Doctrine\Common\EventSubscriber; use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; use function get_class; /** * Reference Listener populates identities for * stored references */ final class MongoDBReferenceListener implements EventSubscriber { /** @var ReferenceRepository */ private $referenceRepository; public function __construct(ReferenceRepository $referenceRepository) { $this->referenceRepository = $referenceRepository; } /** * {@inheritdoc} */ public function getSubscribedEvents() { return ['postPersist']; } /** * Populates identities for stored references * * @return void */ public function postPersist(LifecycleEventArgs $args) { $object = $args->getDocument(); $names = $this->referenceRepository->getReferenceNames($object); if ($names === false) { return; } foreach ($names as $name) { $identity = $args->getDocumentManager() ->getUnitOfWork() ->getDocumentIdentifier($object); $this->referenceRepository->setReferenceIdentity($name, $identity, get_class($object)); } } } data-fixtures/src/Event/Listener/ORMReferenceListener.php 0000644 00000002616 15120025736 0017462 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Event\Listener; use Doctrine\Common\DataFixtures\ReferenceRepository; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LifecycleEventArgs; use function get_class; /** * Reference Listener populates identities for * stored references */ final class ORMReferenceListener implements EventSubscriber { /** @var ReferenceRepository */ private $referenceRepository; public function __construct(ReferenceRepository $referenceRepository) { $this->referenceRepository = $referenceRepository; } /** * {@inheritdoc} */ public function getSubscribedEvents() { // would be better to use onClear, but it is supported only in 2.1 return ['postPersist']; } /** * Populates identities for stored references * * @return void */ public function postPersist(LifecycleEventArgs $args) { $object = $args->getEntity(); $names = $this->referenceRepository->getReferenceNames($object); if ($names === false) { return; } foreach ($names as $name) { $identity = $args->getEntityManager() ->getUnitOfWork() ->getEntityIdentifier($object); $this->referenceRepository->setReferenceIdentity($name, $identity, get_class($object)); } } } data-fixtures/src/Exception/CircularReferenceException.php 0000644 00000000245 15120025736 0020026 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Exception; use LogicException; class CircularReferenceException extends LogicException { } data-fixtures/src/Executor/AbstractExecutor.php 0000644 00000007511 15120025736 0015711 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\Purger\PurgerInterface; use Doctrine\Common\DataFixtures\ReferenceRepository; use Doctrine\Common\DataFixtures\SharedFixtureInterface; use Doctrine\Persistence\ObjectManager; use Exception; use function get_class; use function interface_exists; use function sprintf; /** * Abstract fixture executor. */ abstract class AbstractExecutor { /** * Purger instance for purging database before loading data fixtures * * @var PurgerInterface */ protected $purger; /** * Logger callback for logging messages when loading data fixtures * * @var callable */ protected $logger; /** * Fixture reference repository * * @var ReferenceRepository */ protected $referenceRepository; public function __construct(ObjectManager $manager) { $this->referenceRepository = new ReferenceRepository($manager); } /** @return ReferenceRepository */ public function getReferenceRepository() { return $this->referenceRepository; } public function setReferenceRepository(ReferenceRepository $referenceRepository) { $this->referenceRepository = $referenceRepository; } /** * Sets the Purger instance to use for this executor instance. * * @return void */ public function setPurger(PurgerInterface $purger) { $this->purger = $purger; } /** @return PurgerInterface */ public function getPurger() { return $this->purger; } /** * Set the logger callable to execute with the log() method. * * @param callable $logger * * @return void */ public function setLogger($logger) { $this->logger = $logger; } /** * Logs a message using the logger. * * @param string $message * * @return void */ public function log($message) { $logger = $this->logger; $logger($message); } /** * Load a fixture with the given persistence manager. * * @return void */ public function load(ObjectManager $manager, FixtureInterface $fixture) { if ($this->logger) { $prefix = ''; if ($fixture instanceof OrderedFixtureInterface) { $prefix = sprintf('[%d] ', $fixture->getOrder()); } $this->log('loading ' . $prefix . get_class($fixture)); } // additionally pass the instance of reference repository to shared fixtures if ($fixture instanceof SharedFixtureInterface) { $fixture->setReferenceRepository($this->referenceRepository); } $fixture->load($manager); $manager->clear(); } /** * Purges the database before loading. * * @return void * * @throws Exception if the purger is not defined. */ public function purge() { if ($this->purger === null) { throw new Exception( PurgerInterface::class . ' instance is required if you want to purge the database before loading your data fixtures.' ); } if ($this->logger) { $this->log('purging database'); } $this->purger->purge(); } /** * Executes the given array of data fixtures. * * @param FixtureInterface[] $fixtures Array of fixtures to execute. * @param bool $append Whether to append the data fixtures or purge the database before loading. * * @return void */ abstract public function execute(array $fixtures, $append = false); } interface_exists(ObjectManager::class); data-fixtures/src/Executor/MongoDBExecutor.php 0000644 00000004070 15120025736 0015430 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\Common\DataFixtures\Event\Listener\MongoDBReferenceListener; use Doctrine\Common\DataFixtures\Purger\MongoDBPurger; use Doctrine\Common\DataFixtures\ReferenceRepository; use Doctrine\ODM\MongoDB\DocumentManager; /** * Class responsible for executing data fixtures. */ class MongoDBExecutor extends AbstractExecutor { /** @var DocumentManager */ private $dm; /** @var MongoDBReferenceListener */ private $listener; /** * Construct new fixtures loader instance. * * @param DocumentManager $dm DocumentManager instance used for persistence. */ public function __construct(DocumentManager $dm, ?MongoDBPurger $purger = null) { $this->dm = $dm; if ($purger !== null) { $this->purger = $purger; $this->purger->setDocumentManager($dm); } parent::__construct($dm); $this->listener = new MongoDBReferenceListener($this->referenceRepository); $dm->getEventManager()->addEventSubscriber($this->listener); } /** * Retrieve the DocumentManager instance this executor instance is using. * * @return DocumentManager */ public function getObjectManager() { return $this->dm; } /** @inheritDoc */ public function setReferenceRepository(ReferenceRepository $referenceRepository) { $this->dm->getEventManager()->removeEventListener( $this->listener->getSubscribedEvents(), $this->listener ); $this->referenceRepository = $referenceRepository; $this->listener = new MongoDBReferenceListener($this->referenceRepository); $this->dm->getEventManager()->addEventSubscriber($this->listener); } /** @inheritDoc */ public function execute(array $fixtures, $append = false) { if ($append === false) { $this->purge(); } foreach ($fixtures as $fixture) { $this->load($this->dm, $fixture); } } } data-fixtures/src/Executor/MultipleTransactionORMExecutor.php 0000644 00000001367 15120025736 0020530 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\ORM\EntityManagerInterface; final class MultipleTransactionORMExecutor extends AbstractExecutor { use ORMExecutorCommon; /** @inheritDoc */ public function execute(array $fixtures, $append = false): void { $executor = $this; if ($append === false) { $this->em->wrapInTransaction(static function () use ($executor) { $executor->purge(); }); } foreach ($fixtures as $fixture) { $this->em->wrapInTransaction(static function (EntityManagerInterface $em) use ($executor, $fixture) { $executor->load($em, $fixture); }); } } } data-fixtures/src/Executor/ORMExecutor.php 0000644 00000001306 15120025736 0014577 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\ORM\EntityManagerInterface; /** * Class responsible for executing data fixtures. */ class ORMExecutor extends AbstractExecutor { use ORMExecutorCommon; /** @inheritDoc */ public function execute(array $fixtures, $append = false) { $executor = $this; $this->em->wrapInTransaction(static function (EntityManagerInterface $em) use ($executor, $fixtures, $append) { if ($append === false) { $executor->purge(); } foreach ($fixtures as $fixture) { $executor->load($em, $fixture); } }); } } data-fixtures/src/Executor/ORMExecutorCommon.php 0000644 00000004122 15120025736 0015747 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\Common\DataFixtures\Event\Listener\ORMReferenceListener; use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface; use Doctrine\Common\DataFixtures\ReferenceRepository; use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; /** @internal */ trait ORMExecutorCommon { /** @var EntityManager|EntityManagerDecorator */ private $em; /** @var EntityManagerInterface */ private $originalManager; /** @var ORMReferenceListener */ private $listener; public function __construct(EntityManagerInterface $em, ?ORMPurgerInterface $purger = null) { $this->originalManager = $em; // Make sure, wrapInTransaction() exists on the EM. // To be removed when dropping support for ORM 2 $this->em = $em instanceof EntityManager || $em instanceof EntityManagerDecorator ? $em : new class ($em) extends EntityManagerDecorator { }; if ($purger !== null) { $this->purger = $purger; $this->purger->setEntityManager($em); } parent::__construct($em); $this->listener = new ORMReferenceListener($this->referenceRepository); $em->getEventManager()->addEventSubscriber($this->listener); } /** * Retrieve the EntityManagerInterface instance this executor instance is using. * * @return EntityManagerInterface */ public function getObjectManager() { return $this->originalManager; } /** @inheritDoc */ public function setReferenceRepository(ReferenceRepository $referenceRepository) { $this->em->getEventManager()->removeEventListener( $this->listener->getSubscribedEvents(), $this->listener ); parent::setReferenceRepository($referenceRepository); $this->listener = new ORMReferenceListener($this->referenceRepository); $this->em->getEventManager()->addEventSubscriber($this->listener); } } data-fixtures/src/Executor/PHPCRExecutor.php 0000644 00000003051 15120025736 0015015 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Executor; use Doctrine\Common\DataFixtures\Purger\PHPCRPurger; use Doctrine\ODM\PHPCR\DocumentManagerInterface; use function method_exists; /** * Class responsible for executing data fixtures. */ class PHPCRExecutor extends AbstractExecutor { /** @var DocumentManagerInterface */ private $dm; /** * @param DocumentManagerInterface $dm manager instance used for persisting the fixtures * @param PHPCRPurger|null $purger to remove the current data if append is false */ public function __construct(DocumentManagerInterface $dm, ?PHPCRPurger $purger = null) { parent::__construct($dm); $this->dm = $dm; if ($purger === null) { return; } $purger->setDocumentManager($dm); $this->setPurger($purger); } /** @return DocumentManagerInterface */ public function getObjectManager() { return $this->dm; } /** @inheritDoc */ public function execute(array $fixtures, $append = false) { $that = $this; $function = static function ($dm) use ($append, $that, $fixtures) { if ($append === false) { $that->purge(); } foreach ($fixtures as $fixture) { $that->load($dm, $fixture); } }; if (method_exists($this->dm, 'transactional')) { $this->dm->transactional($function); } else { $function($this->dm); } } } data-fixtures/src/Purger/MongoDBPurger.php 0000644 00000002564 15120025736 0014552 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Purger; use Doctrine\ODM\MongoDB\DocumentManager; /** * Class responsible for purging databases of data before reloading data fixtures. */ class MongoDBPurger implements PurgerInterface { /** @var DocumentManager|null */ private $dm; /** * Construct new purger instance. * * @param DocumentManager|null $dm DocumentManager instance used for persistence. */ public function __construct(?DocumentManager $dm = null) { $this->dm = $dm; } /** * Set the DocumentManager instance this purger instance should use. * * @return void */ public function setDocumentManager(DocumentManager $dm) { $this->dm = $dm; } /** * Retrieve the DocumentManager instance this purger instance is using. * * @return DocumentManager */ public function getObjectManager() { return $this->dm; } /** @inheritDoc */ public function purge() { $metadatas = $this->dm->getMetadataFactory()->getAllMetadata(); foreach ($metadatas as $metadata) { if ($metadata->isMappedSuperclass) { continue; } $this->dm->getDocumentCollection($metadata->name)->drop(); } $this->dm->getSchemaManager()->ensureIndexes(); } } data-fixtures/src/Purger/ORMPurger.php 0000644 00000021266 15120025736 0013722 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Purger; use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Identifier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use function array_reverse; use function array_search; use function assert; use function count; use function is_callable; use function method_exists; use function preg_match; /** * Class responsible for purging databases of data before reloading data fixtures. */ class ORMPurger implements PurgerInterface, ORMPurgerInterface { public const PURGE_MODE_DELETE = 1; public const PURGE_MODE_TRUNCATE = 2; /** @var EntityManagerInterface|null */ private $em; /** * If the purge should be done through DELETE or TRUNCATE statements * * @var int */ private $purgeMode = self::PURGE_MODE_DELETE; /** * Table/view names to be excluded from purge * * @var string[] */ private $excluded; /** * Construct new purger instance. * * @param EntityManagerInterface|null $em EntityManagerInterface instance used for persistence. * @param string[] $excluded array of table/view names to be excluded from purge */ public function __construct(?EntityManagerInterface $em = null, array $excluded = []) { $this->em = $em; $this->excluded = $excluded; } /** * Set the purge mode * * @param int $mode * * @return void */ public function setPurgeMode($mode) { $this->purgeMode = $mode; } /** * Get the purge mode * * @return int */ public function getPurgeMode() { return $this->purgeMode; } /** @inheritDoc */ public function setEntityManager(EntityManagerInterface $em) { $this->em = $em; } /** * Retrieve the EntityManagerInterface instance this purger instance is using. * * @return EntityManagerInterface */ public function getObjectManager() { return $this->em; } /** @inheritDoc */ public function purge() { $classes = []; foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) { if ($metadata->isMappedSuperclass || (isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) { continue; } $classes[] = $metadata; } $commitOrder = $this->getCommitOrder($this->em, $classes); // Get platform parameters $platform = $this->em->getConnection()->getDatabasePlatform(); // Drop association tables first $orderedTables = $this->getAssociationTables($commitOrder, $platform); // Drop tables in reverse commit order for ($i = count($commitOrder) - 1; $i >= 0; --$i) { $class = $commitOrder[$i]; if ( (isset($class->isEmbeddedClass) && $class->isEmbeddedClass) || $class->isMappedSuperclass || ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName) ) { continue; } $orderedTables[] = $this->getTableName($class, $platform); } $connection = $this->em->getConnection(); $filterExpr = method_exists( $connection->getConfiguration(), 'getFilterSchemaAssetsExpression' ) ? $connection->getConfiguration()->getFilterSchemaAssetsExpression() : null; $emptyFilterExpression = empty($filterExpr); $schemaAssetsFilter = method_exists( $connection->getConfiguration(), 'getSchemaAssetsFilter' ) ? $connection->getConfiguration()->getSchemaAssetsFilter() : null; foreach ($orderedTables as $tbl) { // If we have a filter expression, check it and skip if necessary if (! $emptyFilterExpression && ! preg_match($filterExpr, $tbl)) { continue; } // If the table is excluded, skip it as well if (array_search($tbl, $this->excluded) !== false) { continue; } // Support schema asset filters as presented in if (is_callable($schemaAssetsFilter) && ! $schemaAssetsFilter($tbl)) { continue; } if ($this->purgeMode === self::PURGE_MODE_DELETE) { $connection->executeStatement($this->getDeleteFromTableSQL($tbl, $platform)); } else { $connection->executeStatement($platform->getTruncateTableSQL($tbl, true)); } } } /** * @param ClassMetadata[] $classes * * @return ClassMetadata[] */ private function getCommitOrder(EntityManagerInterface $em, array $classes) { $sorter = new TopologicalSorter(); foreach ($classes as $class) { if (! $sorter->hasNode($class->name)) { $sorter->addNode($class->name, $class); } // $class before its parents foreach ($class->parentClasses as $parentClass) { $parentClass = $em->getClassMetadata($parentClass); $parentClassName = $parentClass->getName(); if (! $sorter->hasNode($parentClassName)) { $sorter->addNode($parentClassName, $parentClass); } $sorter->addDependency($class->name, $parentClassName); } foreach ($class->associationMappings as $assoc) { if (! $assoc['isOwningSide']) { continue; } $targetClass = $em->getClassMetadata($assoc['targetEntity']); assert($targetClass instanceof ClassMetadata); $targetClassName = $targetClass->getName(); if (! $sorter->hasNode($targetClassName)) { $sorter->addNode($targetClassName, $targetClass); } // add dependency ($targetClass before $class) $sorter->addDependency($targetClassName, $class->name); // parents of $targetClass before $class, too foreach ($targetClass->parentClasses as $parentClass) { $parentClass = $em->getClassMetadata($parentClass); $parentClassName = $parentClass->getName(); if (! $sorter->hasNode($parentClassName)) { $sorter->addNode($parentClassName, $parentClass); } $sorter->addDependency($parentClassName, $class->name); } } } return array_reverse($sorter->sort()); } /** * @param ClassMetadata[] $classes * * @return string[] */ private function getAssociationTables(array $classes, AbstractPlatform $platform) { $associationTables = []; foreach ($classes as $class) { foreach ($class->associationMappings as $assoc) { if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) { continue; } $associationTables[] = $this->getJoinTableName($assoc, $class, $platform); } } return $associationTables; } private function getTableName(ClassMetadata $class, AbstractPlatform $platform): string { if (isset($class->table['schema']) && ! method_exists($class, 'getSchemaName')) { return $class->table['schema'] . '.' . $this->em->getConfiguration() ->getQuoteStrategy() ->getTableName($class, $platform); } return $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform); } /** @param mixed[] $assoc */ private function getJoinTableName( array $assoc, ClassMetadata $class, AbstractPlatform $platform ): string { if (isset($assoc['joinTable']['schema']) && ! method_exists($class, 'getSchemaName')) { return $assoc['joinTable']['schema'] . '.' . $this->em->getConfiguration() ->getQuoteStrategy() ->getJoinTableName($assoc, $class, $platform); } return $this->em->getConfiguration()->getQuoteStrategy()->getJoinTableName($assoc, $class, $platform); } private function getDeleteFromTableSQL(string $tableName, AbstractPlatform $platform): string { $tableIdentifier = new Identifier($tableName); return 'DELETE FROM ' . $tableIdentifier->getQuotedName($platform); } } data-fixtures/src/Purger/ORMPurgerInterface.php 0000644 00000000617 15120025736 0015540 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Purger; use Doctrine\ORM\EntityManagerInterface; /** * ORMPurgerInterface */ interface ORMPurgerInterface extends PurgerInterface { /** * Set the EntityManagerInterface instance this purger instance should use. * * @return void */ public function setEntityManager(EntityManagerInterface $em); } data-fixtures/src/Purger/PHPCRPurger.php 0000644 00000001627 15120025736 0014140 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Purger; use Doctrine\ODM\PHPCR\DocumentManager; use Doctrine\ODM\PHPCR\DocumentManagerInterface; use PHPCR\Util\NodeHelper; /** * Class responsible for purging databases of data before reloading data fixtures. */ class PHPCRPurger implements PurgerInterface { /** @var DocumentManagerInterface|null */ private $dm; public function __construct(?DocumentManagerInterface $dm = null) { $this->dm = $dm; } public function setDocumentManager(DocumentManager $dm) { $this->dm = $dm; } /** @return DocumentManagerInterface|null */ public function getObjectManager() { return $this->dm; } /** @inheritDoc */ public function purge() { $session = $this->dm->getPhpcrSession(); NodeHelper::purgeWorkspace($session); $session->save(); } } data-fixtures/src/Purger/PurgerInterface.php 0000644 00000000427 15120025736 0015161 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Purger; /** * PurgerInterface */ interface PurgerInterface { /** * Purge the data from the database for the given EntityManager. * * @return void */ public function purge(); } data-fixtures/src/Sorter/TopologicalSorter.php 0000644 00000011677 15120025736 0015572 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Sorter; use Doctrine\Common\DataFixtures\Exception\CircularReferenceException; use Doctrine\ORM\Mapping\ClassMetadata; use RuntimeException; use function get_class; use function sprintf; /** * TopologicalSorter is an ordering algorithm for directed graphs (DG) and/or * directed acyclic graphs (DAG) by using a depth-first searching (DFS) to * traverse the graph built in memory. * This algorithm have a linear running time based on nodes (V) and dependency * between the nodes (E), resulting in a computational complexity of O(V + E). * * @internal this class is to be used only by data-fixtures internals: do not * rely on it in your own libraries/applications. */ class TopologicalSorter { /** * Matrix of nodes (aka. vertex). * Keys are provided hashes and values are the node definition objects. * * @var Vertex[] */ private $nodeList = []; /** * Volatile variable holding calculated nodes during sorting process. * * @var ClassMetadata[] */ private $sortedNodeList = []; /** * Allow or not cyclic dependencies * * @var bool */ private $allowCyclicDependencies; /** @param bool $allowCyclicDependencies */ public function __construct($allowCyclicDependencies = true) { $this->allowCyclicDependencies = $allowCyclicDependencies; } /** * Adds a new node (vertex) to the graph, assigning its hash and value. * * @param string $hash * * @return void */ public function addNode($hash, ClassMetadata $node) { $this->nodeList[$hash] = new Vertex($node); } /** * Checks the existence of a node in the graph. * * @param string $hash * * @return bool */ public function hasNode($hash) { return isset($this->nodeList[$hash]); } /** * Adds a new dependency (edge) to the graph using their hashes. * * @param string $fromHash * @param string $toHash * * @return void */ public function addDependency($fromHash, $toHash) { $definition = $this->nodeList[$fromHash]; $definition->dependencyList[] = $toHash; } /** * Return a valid order list of all current nodes. * The desired topological sorting is the postorder of these searches. * * Note: Highly performance-sensitive method. * * @return ClassMetadata[] * * @throws RuntimeException * @throws CircularReferenceException */ public function sort() { foreach ($this->nodeList as $definition) { if ($definition->state !== Vertex::NOT_VISITED) { continue; } $this->visit($definition); } $sortedList = $this->sortedNodeList; $this->nodeList = []; $this->sortedNodeList = []; return $sortedList; } /** * Visit a given node definition for reordering. * * Note: Highly performance-sensitive method. * * @return void * * @throws RuntimeException * @throws CircularReferenceException */ private function visit(Vertex $definition) { $definition->state = Vertex::IN_PROGRESS; foreach ($definition->dependencyList as $dependency) { if (! isset($this->nodeList[$dependency])) { throw new RuntimeException(sprintf( 'Fixture "%s" has a dependency of fixture "%s", but it not listed to be loaded.', get_class($definition->value), $dependency )); } $childDefinition = $this->nodeList[$dependency]; // allow self referencing classes if ($definition === $childDefinition) { continue; } switch ($childDefinition->state) { case Vertex::VISITED: break; case Vertex::IN_PROGRESS: if (! $this->allowCyclicDependencies) { throw new CircularReferenceException( sprintf( <<<'EXCEPTION' Graph contains cyclic dependency between the classes "%s" and "%s". An example of this problem would be the following: Class C has class B as its dependency. Then, class B has class A has its dependency. Finally, class A has class C as its dependency. EXCEPTION , $definition->value->getName(), $childDefinition->value->getName() ) ); } break; case Vertex::NOT_VISITED: $this->visit($childDefinition); } } $definition->state = Vertex::VISITED; $this->sortedNodeList[] = $definition->value; } } data-fixtures/src/Sorter/Vertex.php 0000644 00000001650 15120025736 0013362 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures\Sorter; use Doctrine\ORM\Mapping\ClassMetadata; /** * @internal this class is to be used only by data-fixtures internals: do not * rely on it in your own libraries/applications. This class is * designed to work with {@see \Doctrine\Common\DataFixtures\Sorter\TopologicalSorter} * only. */ class Vertex { public const NOT_VISITED = 0; public const IN_PROGRESS = 1; public const VISITED = 2; /** @var int one of either {@see self::NOT_VISITED}, {@see self::IN_PROGRESS} or {@see self::VISITED}. */ public $state = self::NOT_VISITED; /** @var ClassMetadata Actual node value */ public $value; /** @var string[] Map of node dependencies defined as hashes. */ public $dependencyList = []; public function __construct(ClassMetadata $value) { $this->value = $value; } } data-fixtures/src/AbstractFixture.php 0000644 00000006660 15120025736 0013747 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; use BadMethodCallException; use Doctrine\Deprecations\Deprecation; use function assert; /** * Abstract Fixture class helps to manage references * between fixture classes in order to set relations * among other fixtures */ abstract class AbstractFixture implements SharedFixtureInterface { /** * Fixture reference repository * * @var ReferenceRepository|null */ protected $referenceRepository; /** * {@inheritdoc} */ public function setReferenceRepository(ReferenceRepository $referenceRepository) { $this->referenceRepository = $referenceRepository; } private function getReferenceRepository(): ReferenceRepository { assert($this->referenceRepository !== null); return $this->referenceRepository; } /** * Set the reference entry identified by $name * and referenced to managed $object. If $name * already is set, it overrides it * * @see Doctrine\Common\DataFixtures\ReferenceRepository::setReference * * @param string $name * @param object $object - managed object * * @return void */ public function setReference($name, $object) { $this->getReferenceRepository()->setReference($name, $object); } /** * Set the reference entry identified by $name * and referenced to managed $object. If $name * already is set, it throws a * BadMethodCallException exception * * @see Doctrine\Common\DataFixtures\ReferenceRepository::addReference * * @param string $name * @param object $object - managed object * * @return void * * @throws BadMethodCallException - if repository already has a reference by $name. */ public function addReference($name, $object) { $this->getReferenceRepository()->addReference($name, $object); } /** * Loads an object using stored reference * named by $name * * @see Doctrine\Common\DataFixtures\ReferenceRepository::getReference * * @param string $name * @psalm-param class-string<T>|null $class * * @return object * @psalm-return ($class is null ? object : T) * * @template T of object */ public function getReference($name, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } return $this->getReferenceRepository()->getReference($name, $class); } /** * Check if an object is stored using reference * named by $name * * @see Doctrine\Common\DataFixtures\ReferenceRepository::hasReference * * @param string $name * @psalm-param class-string $class * * @return bool */ public function hasReference($name, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } return $this->getReferenceRepository()->hasReference($name, $class); } } data-fixtures/src/DependentFixtureInterface.php 0000644 00000000701 15120025736 0015721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; /** * DependentFixtureInterface needs to be implemented by fixtures which depend on other fixtures */ interface DependentFixtureInterface { /** * This method must return an array of fixtures classes * on which the implementing class depends on * * @psalm-return array<class-string<FixtureInterface>> */ public function getDependencies(); } data-fixtures/src/FixtureInterface.php 0000644 00000000625 15120025736 0014077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; use Doctrine\Persistence\ObjectManager; use function interface_exists; /** * Interface contract for fixture classes to implement. */ interface FixtureInterface { /** * Load data fixtures with the passed EntityManager */ public function load(ObjectManager $manager); } interface_exists(ObjectManager::class); data-fixtures/src/Loader.php 0000644 00000033206 15120025736 0012037 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; use ArrayIterator; use Doctrine\Common\DataFixtures\Exception\CircularReferenceException; use InvalidArgumentException; use Iterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; use RuntimeException; use SplFileInfo; use function array_keys; use function array_merge; use function asort; use function class_exists; use function class_implements; use function count; use function get_class; use function get_declared_classes; use function implode; use function in_array; use function is_array; use function is_dir; use function is_readable; use function realpath; use function sort; use function sprintf; use function usort; /** * Class responsible for loading data fixture classes. */ class Loader { /** * Array of fixture object instances to execute. * * @psalm-var array<class-string<FixtureInterface>, FixtureInterface> */ private $fixtures = []; /** * Array of ordered fixture object instances. * * @psalm-var array<class-string<FixtureInterface>|int, FixtureInterface> */ private $orderedFixtures = []; /** * Determines if we must order fixtures by number * * @var bool */ private $orderFixturesByNumber = false; /** * Determines if we must order fixtures by its dependencies * * @var bool */ private $orderFixturesByDependencies = false; /** * The file extension of fixture files. * * @var string */ private $fileExtension = '.php'; /** * Find fixtures classes in a given directory and load them. * * @param string $dir Directory to find fixture classes in. * * @return array $fixtures Array of loaded fixture object instances. */ public function loadFromDirectory($dir) { if (! is_dir($dir)) { throw new InvalidArgumentException(sprintf('"%s" does not exist', $dir)); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY ); return $this->loadFromIterator($iterator); } /** * Find fixtures classes in a given file and load them. * * @param string $fileName File to find fixture classes in. * * @return array $fixtures Array of loaded fixture object instances. */ public function loadFromFile($fileName) { if (! is_readable($fileName)) { throw new InvalidArgumentException(sprintf('"%s" does not exist or is not readable', $fileName)); } $iterator = new ArrayIterator([new SplFileInfo($fileName)]); return $this->loadFromIterator($iterator); } /** * Has fixture? * * @param FixtureInterface $fixture * * @return bool */ public function hasFixture($fixture) { return isset($this->fixtures[get_class($fixture)]); } /** * Get a specific fixture instance * * @param string $className * * @return FixtureInterface */ public function getFixture($className) { if (! isset($this->fixtures[$className])) { throw new InvalidArgumentException(sprintf( '"%s" is not a registered fixture', $className )); } return $this->fixtures[$className]; } /** * Add a fixture object instance to the loader. */ public function addFixture(FixtureInterface $fixture) { $fixtureClass = get_class($fixture); if (isset($this->fixtures[$fixtureClass])) { return; } if ($fixture instanceof OrderedFixtureInterface && $fixture instanceof DependentFixtureInterface) { throw new InvalidArgumentException(sprintf( 'Class "%s" can\'t implement "%s" and "%s" at the same time.', get_class($fixture), 'OrderedFixtureInterface', 'DependentFixtureInterface' )); } $this->fixtures[$fixtureClass] = $fixture; if ($fixture instanceof OrderedFixtureInterface) { $this->orderFixturesByNumber = true; } elseif ($fixture instanceof DependentFixtureInterface) { $this->orderFixturesByDependencies = true; foreach ($fixture->getDependencies() as $class) { if (! class_exists($class)) { continue; } $this->addFixture($this->createFixture($class)); } } } /** * Returns the array of data fixtures to execute. * * @psalm-return array<class-string<FixtureInterface>|int, FixtureInterface> */ public function getFixtures() { $this->orderedFixtures = []; if ($this->orderFixturesByNumber) { $this->orderFixturesByNumber(); } if ($this->orderFixturesByDependencies) { $this->orderFixturesByDependencies(); } if (! $this->orderFixturesByNumber && ! $this->orderFixturesByDependencies) { $this->orderedFixtures = $this->fixtures; } return $this->orderedFixtures; } /** * Check if a given fixture is transient and should not be considered a data fixtures * class. * * @psalm-param class-string<object> $className * * @return bool */ public function isTransient($className) { $rc = new ReflectionClass($className); if ($rc->isAbstract()) { return true; } $interfaces = class_implements($className); return ! in_array(FixtureInterface::class, $interfaces); } /** * Creates the fixture object from the class. * * @param string $class * * @return FixtureInterface */ protected function createFixture($class) { return new $class(); } /** * Orders fixtures by number * * @todo maybe there is a better way to handle reordering */ private function orderFixturesByNumber(): void { $this->orderedFixtures = $this->fixtures; usort($this->orderedFixtures, static function (FixtureInterface $a, FixtureInterface $b): int { if ($a instanceof OrderedFixtureInterface && $b instanceof OrderedFixtureInterface) { if ($a->getOrder() === $b->getOrder()) { return 0; } return $a->getOrder() < $b->getOrder() ? -1 : 1; } if ($a instanceof OrderedFixtureInterface) { return $a->getOrder() === 0 ? 0 : 1; } if ($b instanceof OrderedFixtureInterface) { return $b->getOrder() === 0 ? 0 : -1; } return 0; }); } /** * Orders fixtures by dependencies * * @return void */ private function orderFixturesByDependencies() { /** @psalm-var array<class-string<DependentFixtureInterface>, int> */ $sequenceForClasses = []; // If fixtures were already ordered by number then we need // to remove classes which are not instances of OrderedFixtureInterface // in case fixtures implementing DependentFixtureInterface exist. // This is because, in that case, the method orderFixturesByDependencies // will handle all fixtures which are not instances of // OrderedFixtureInterface if ($this->orderFixturesByNumber) { $count = count($this->orderedFixtures); for ($i = 0; $i < $count; ++$i) { if ($this->orderedFixtures[$i] instanceof OrderedFixtureInterface) { continue; } unset($this->orderedFixtures[$i]); } } // First we determine which classes has dependencies and which don't foreach ($this->fixtures as $fixture) { $fixtureClass = get_class($fixture); if ($fixture instanceof OrderedFixtureInterface) { continue; } if ($fixture instanceof DependentFixtureInterface) { $dependenciesClasses = $fixture->getDependencies(); $this->validateDependencies($dependenciesClasses); if (! is_array($dependenciesClasses) || empty($dependenciesClasses)) { throw new InvalidArgumentException(sprintf( 'Method "%s" in class "%s" must return an array of classes which are dependencies for the fixture, and it must be NOT empty.', 'getDependencies', $fixtureClass )); } if (in_array($fixtureClass, $dependenciesClasses)) { throw new InvalidArgumentException(sprintf( 'Class "%s" can\'t have itself as a dependency', $fixtureClass )); } // We mark this class as unsequenced $sequenceForClasses[$fixtureClass] = -1; } else { // This class has no dependencies, so we assign 0 $sequenceForClasses[$fixtureClass] = 0; } } // Now we order fixtures by sequence $sequence = 1; $lastCount = -1; while (($count = count($unsequencedClasses = $this->getUnsequencedClasses($sequenceForClasses))) > 0 && $count !== $lastCount) { foreach ($unsequencedClasses as $key => $class) { $fixture = $this->fixtures[$class]; $dependencies = $fixture->getDependencies(); $unsequencedDependencies = $this->getUnsequencedClasses($sequenceForClasses, $dependencies); if (count($unsequencedDependencies) !== 0) { continue; } $sequenceForClasses[$class] = $sequence++; } $lastCount = $count; } $orderedFixtures = []; // If there're fixtures unsequenced left and they couldn't be sequenced, // it means we have a circular reference if ($count > 0) { $msg = 'Classes "%s" have produced a CircularReferenceException. '; $msg .= 'An example of this problem would be the following: Class C has class B as its dependency. '; $msg .= 'Then, class B has class A has its dependency. Finally, class A has class C as its dependency. '; $msg .= 'This case would produce a CircularReferenceException.'; throw new CircularReferenceException(sprintf($msg, implode(',', $unsequencedClasses))); } // We order the classes by sequence asort($sequenceForClasses); foreach ($sequenceForClasses as $class => $sequence) { // If fixtures were ordered $orderedFixtures[] = $this->fixtures[$class]; } $this->orderedFixtures = array_merge($this->orderedFixtures, $orderedFixtures); } /** @psalm-param iterable<class-string> $dependenciesClasses */ private function validateDependencies(iterable $dependenciesClasses): bool { $loadedFixtureClasses = array_keys($this->fixtures); foreach ($dependenciesClasses as $class) { if (! in_array($class, $loadedFixtureClasses)) { throw new RuntimeException(sprintf( 'Fixture "%s" was declared as a dependency, but it should be added in fixture loader first.', $class )); } } return true; } /** * @psalm-param array<class-string<DependentFixtureInterface>, int> $sequences * @psalm-param iterable<class-string<FixtureInterface>>|null $classes * * @psalm-return array<class-string<FixtureInterface>> */ private function getUnsequencedClasses(array $sequences, ?iterable $classes = null): array { $unsequencedClasses = []; if ($classes === null) { $classes = array_keys($sequences); } foreach ($classes as $class) { if (! isset($sequences[$class]) || $sequences[$class] !== -1) { continue; } $unsequencedClasses[] = $class; } return $unsequencedClasses; } /** * Load fixtures from files contained in iterator. * * @psalm-param Iterator<SplFileInfo> $iterator Iterator over files from * which fixtures should be loaded. * * @psalm-return list<FixtureInterface> $fixtures Array of loaded fixture object instances. */ private function loadFromIterator(Iterator $iterator): array { $includedFiles = []; foreach ($iterator as $file) { $fileName = $file->getBasename($this->fileExtension); if ($fileName === $file->getBasename()) { continue; } $sourceFile = realpath($file->getPathName()); require_once $sourceFile; $includedFiles[] = $sourceFile; } $fixtures = []; $declared = get_declared_classes(); // Make the declared classes order deterministic sort($declared); foreach ($declared as $className) { $reflClass = new ReflectionClass($className); $sourceFile = $reflClass->getFileName(); if (! in_array($sourceFile, $includedFiles) || $this->isTransient($className)) { continue; } $fixture = $this->createFixture($className); $fixtures[] = $fixture; $this->addFixture($fixture); } return $fixtures; } } data-fixtures/src/OrderedFixtureInterface.php 0000644 00000000604 15120025736 0015401 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; /** * Ordered Fixture interface needs to be implemented * by fixtures, which needs to have a specific order * when being loaded by directory scan for example */ interface OrderedFixtureInterface { /** * Get the order of this fixture * * @return int */ public function getOrder(); } data-fixtures/src/ProxyReferenceRepository.php 0000644 00000007016 15120025736 0015671 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; use function file_exists; use function file_get_contents; use function file_put_contents; use function get_class; use function serialize; use function unserialize; /** * Proxy reference repository * * Allow data fixture references and identities to be persisted when cached data fixtures * are pre-loaded, for example, by LiipFunctionalTestBundle\Test\WebTestCase loadFixtures(). */ class ProxyReferenceRepository extends ReferenceRepository { /** * Serialize reference repository * * @return string */ public function serialize() { $unitOfWork = $this->getManager()->getUnitOfWork(); $simpleReferences = []; foreach ($this->getReferences() as $name => $reference) { $className = $this->getRealClass(get_class($reference)); $simpleReferences[$name] = [$className, $this->getIdentifier($reference, $unitOfWork)]; } return serialize([ 'references' => $simpleReferences, // For BC, remove in next major. 'identities' => $this->getIdentities(), // For BC, remove in next major. 'identitiesByClass' => $this->getIdentitiesByClass(), ]); } /** * Unserialize reference repository * * @param string $serializedData Serialized data * * @return void */ public function unserialize($serializedData) { $repositoryData = unserialize($serializedData); // For BC, remove in next major. if (! isset($repositoryData['identitiesByClass'])) { $references = $repositoryData['references']; foreach ($references as $name => $proxyReference) { $this->setReference( $name, $this->getManager()->getReference( $proxyReference[0], // entity class name $proxyReference[1] // identifiers ) ); } $identities = $repositoryData['identities']; foreach ($identities as $name => $identity) { $this->setReferenceIdentity($name, $identity); } return; } foreach ($repositoryData['identitiesByClass'] as $className => $identities) { foreach ($identities as $name => $identity) { $this->setReference( $name, $this->getManager()->getReference( $className, $identity ) ); $this->setReferenceIdentity($name, $identity, $className); } } } /** * Load data fixture reference repository * * @param string $baseCacheName Base cache name * * @return bool */ public function load($baseCacheName) { $filename = $baseCacheName . '.ser'; if (! file_exists($filename)) { return false; } $serializedData = file_get_contents($filename); if ($serializedData === false) { return false; } $this->unserialize($serializedData); return true; } /** * Save data fixture reference repository * * @param string $baseCacheName Base cache name * * @return void */ public function save($baseCacheName) { $serializedData = $this->serialize(); file_put_contents($baseCacheName . '.ser', $serializedData); } } data-fixtures/src/ReferenceRepository.php 0000644 00000026535 15120025736 0014636 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; use BadMethodCallException; use Doctrine\Deprecations\Deprecation; use Doctrine\ODM\PHPCR\DocumentManager as PhpcrDocumentManager; use Doctrine\Persistence\ObjectManager; use OutOfBoundsException; use function array_key_exists; use function array_keys; use function get_class; use function method_exists; use function sprintf; /** * ReferenceRepository class manages references for * fixtures in order to easily support the relations * between fixtures */ class ReferenceRepository { /** * List of named references to the fixture objects * gathered during fixure loading * * @psalm-var array<string, object> */ private $references = []; /** * List of named references to the fixture objects * gathered during fixure loading * * @psalm-var array<class-string, array<string, object>> */ private $referencesByClass = []; /** * List of identifiers stored for references * in case a reference gets no longer managed, it will * use a proxy referenced by this identity * * @psalm-var array<string, mixed> */ private $identities = []; /** * List of identifiers stored for references * in case a reference gets no longer managed, it will * use a proxy referenced by this identity * * @psalm-var array<class-string, array<string, mixed>> */ private $identitiesByClass = []; /** * Currently used object manager * * @var ObjectManager */ private $manager; public function __construct(ObjectManager $manager) { $this->manager = $manager; } /** * Get identifier for a unit of work * * @param object $reference Reference object * @param object $uow Unit of work * * @return array */ protected function getIdentifier($reference, $uow) { // In case Reference is not yet managed in UnitOfWork if (! $this->hasIdentifier($reference)) { $class = $this->manager->getClassMetadata(get_class($reference)); return $class->getIdentifierValues($reference); } // Dealing with ORM UnitOfWork if (method_exists($uow, 'getEntityIdentifier')) { return $uow->getEntityIdentifier($reference); } // PHPCR ODM UnitOfWork if ($this->manager instanceof PhpcrDocumentManager) { return $uow->getDocumentId($reference); } // ODM UnitOfWork return $uow->getDocumentIdentifier($reference); } /** * Set the reference entry identified by $name * and referenced to $reference. If $name * already is set, it overrides it * * @param string $name * @param object $reference * * @return void */ public function setReference($name, $reference) { $class = $this->getRealClass(get_class($reference)); $this->referencesByClass[$class][$name] = $reference; // For BC, to be removed in next major. $this->references[$name] = $reference; if (! $this->hasIdentifier($reference)) { return; } // in case if reference is set after flush, store its identity $uow = $this->manager->getUnitOfWork(); $identifier = $this->getIdentifier($reference, $uow); $this->identitiesByClass[$class][$name] = $identifier; // For BC, to be removed in next major. $this->identities[$name] = $identifier; } /** * Store the identifier of a reference * * @param string $name * @param mixed $identity * @param class-string|null $class * * @return void */ public function setReferenceIdentity($name, $identity, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } $this->identitiesByClass[$class][$name] = $identity; // For BC, to be removed in next major. $this->identities[$name] = $identity; } /** * Set the reference entry identified by $name * and referenced to managed $object. $name must * not be set yet * * Notice: in case if identifier is generated after * the record is inserted, be sure tu use this method * after $object is flushed * * @param string $name * @param object $object - managed object * * @return void * * @throws BadMethodCallException - if repository already has a reference by $name. */ public function addReference($name, $object) { // For BC, to be removed in next major. if (isset($this->references[$name])) { throw new BadMethodCallException(sprintf( 'Reference to "%s" already exists, use method setReference() in order to override it', $name )); } $class = $this->getRealClass(get_class($object)); if (isset($this->referencesByClass[$class][$name])) { throw new BadMethodCallException(sprintf( 'Reference to "%s" for class "%s" already exists, use method setReference() in order to override it', $name, $class )); } $this->setReference($name, $object); } /** * Loads an object using stored reference * named by $name * * @param string $name * @psalm-param class-string<T>|null $class * * @return object * @psalm-return ($class is null ? object : T) * * @throws OutOfBoundsException - if repository does not exist. * * @template T of object */ public function getReference($name, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } if (! $this->hasReference($name, $class)) { // For BC, to be removed in next major. if ($class === null) { throw new OutOfBoundsException(sprintf('Reference to "%s" does not exist', $name)); } throw new OutOfBoundsException(sprintf('Reference to "%s" for class "%s" does not exist', $name, $class)); } $reference = $class === null ? $this->references[$name] // For BC, to be removed in next major. : $this->referencesByClass[$class][$name]; $identity = $class === null ? ($this->identities[$name] ?? null) // For BC, to be removed in next major. : ($this->identitiesByClass[$class][$name] ?? null); if ($class === null) { // For BC, to be removed in next major. $class = $this->getRealClass(get_class($reference)); } $meta = $this->manager->getClassMetadata($class); if (! $this->manager->contains($reference) && $identity !== null) { $reference = $this->manager->getReference($meta->name, $identity); $this->references[$name] = $reference; // already in identity map $this->referencesByClass[$class][$name] = $reference; // already in identity map } return $reference; } /** * Check if an object is stored using reference * named by $name * * @param string $name * @psalm-param class-string $class * * @return bool */ public function hasReference($name, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } return $class === null ? isset($this->references[$name]) // For BC, to be removed in next major. : isset($this->referencesByClass[$class][$name]); } /** * Searches for reference names in the * list of stored references * * @param object $reference * * @return array<string> */ public function getReferenceNames($reference) { $class = $this->getRealClass(get_class($reference)); if (! isset($this->referencesByClass[$class])) { return []; } return array_keys($this->referencesByClass[$class], $reference, true); } /** * Checks if reference has identity stored * * @param string $name * @param class-string|null $class * * @return bool */ public function hasIdentity($name, ?string $class = null) { if ($class === null) { Deprecation::trigger( 'doctrine/data-fixtures', 'https://github.com/doctrine/data-fixtures/pull/409', 'Argument $class of %s() will be mandatory in 2.0.', __METHOD__ ); } return $class === null ? array_key_exists($name, $this->identities) // For BC, to be removed in next major. : array_key_exists($class, $this->identitiesByClass) && array_key_exists($name, $this->identitiesByClass[$class]); } /** * @deprecated in favor of getIdentitiesByClass * * Get all stored identities * * @psalm-return array<string, object> */ public function getIdentities() { return $this->identities; } /** * Get all stored identities * * @psalm-return array<class-string, array<string, object>> */ public function getIdentitiesByClass(): array { return $this->identitiesByClass; } /** * @deprecated in favor of getReferencesByClass * * Get all stored references * * @psalm-return array<string, object> */ public function getReferences() { return $this->references; } /** * Get all stored references * * @psalm-return array<class-string, array<string, object>> */ public function getReferencesByClass(): array { return $this->referencesByClass; } /** * Get object manager * * @return ObjectManager */ public function getManager() { return $this->manager; } /** * Get real class name of a reference that could be a proxy * * @param string $className Class name of reference object * * @return string */ protected function getRealClass($className) { return $this->manager->getClassMetadata($className)->getName(); } /** * Checks if object has identifier already in unit of work. * * @param object $reference * * @return bool */ private function hasIdentifier($reference) { // in case if reference is set after flush, store its identity $uow = $this->manager->getUnitOfWork(); if ($this->manager instanceof PhpcrDocumentManager) { return $uow->contains($reference); } return $uow->isInIdentityMap($reference); } } data-fixtures/src/SharedFixtureInterface.php 0000644 00000000633 15120025736 0015225 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\DataFixtures; /** * Shared Fixture interface needs to be implemented * by fixtures, which needs some references to be shared * among other fixture classes in order to maintain * relation mapping */ interface SharedFixtureInterface extends FixtureInterface { public function setReferenceRepository(ReferenceRepository $referenceRepository); } data-fixtures/.doctrine-project.json 0000644 00000001441 15120025736 0013551 0 ustar 00 { "active": true, "name": "Data fixtures", "slug": "data-fixtures", "docsSlug": "doctrine-data-fixtures", "versions": [ { "name": "1.6", "branchName": "1.6.x", "slug": "1.6", "upcoming": true }, { "name": "1.5", "branchName": "1.5.x", "slug": "latest", "current": true, "aliases": [ "current", "stable" ] }, { "name": "1.4", "branchName": "1.4.x", "slug": "1.4", "maintained": false }, { "name": "1.3", "branchName": "1.3", "slug": "1.3", "maintained": false } ] } data-fixtures/LICENSE 0000644 00000002052 15120025736 0010331 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. data-fixtures/README.md 0000644 00000001373 15120025736 0010610 0 ustar 00 # Doctrine Data Fixtures Extension [](https://github.com/doctrine/data-fixtures/actions) This extension aims to provide a simple way to manage and execute the loading of data fixtures for the [Doctrine ORM or ODM](http://www.doctrine-project.org/). ## More resources: * [Website](http://www.doctrine-project.org) * [Documentation](https://www.doctrine-project.org/projects/doctrine-data-fixtures/en/latest/index.html) ## Running the tests: Phpunit is included in the dev requirements of this package. To setup and run tests follow these steps: - go to the root directory of data-fixtures - run: **composer install --dev** - run: **vendor/bin/phpunit** data-fixtures/UPGRADE.md 0000644 00000001772 15120025737 0010746 0 ustar 00 Note about upgrading: Doctrine uses static and runtime mechanisms to raise awareness about deprecated code. - Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or Static Analysis tools (like Psalm, phpstan) - Use of our low-overhead runtime deprecation API, details: https://github.com/doctrine/deprecations/ # Upgrade to 1.6 ## BC BREAK: `CircularReferenceException` no longer extends `Doctrine\Common\CommonException` We don't think anyone catches this exception in a `catch (CommonException)` statement. ## `doctrine/data-fixtures` no longer requires `doctrine/common` If you rely on types from `doctrine/common`, you should require that package, regardless of whether other packages require it. # Between v1.0.0-ALPHA1 and v1.0.0-ALPHA2 The FixtureInterface was changed from interface FixtureInterface { load($manager); } to use Doctrine\Common\Persistence\ObjectManager; interface FixtureInterface { load(ObjectManager $manager); } data-fixtures/composer.json 0000644 00000003366 15120025737 0012060 0 ustar 00 { "name": "doctrine/data-fixtures", "type": "library", "description": "Data Fixtures for all Doctrine Object Managers", "keywords": [ "database" ], "homepage": "https://www.doctrine-project.org", "license": "MIT", "authors": [ { "name": "Jonathan Wage", "email": "jonwage@gmail.com" } ], "require": { "php": "^7.2 || ^8.0", "doctrine/deprecations": "^0.5.3 || ^1.0", "doctrine/persistence": "^1.3.3 || ^2.0 || ^3.0" }, "conflict": { "doctrine/dbal": "<2.13", "doctrine/orm": "<2.12", "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { "ext-sqlite3": "*", "doctrine/coding-standard": "^11.0", "doctrine/dbal": "^2.13 || ^3.0", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.12", "phpstan/phpstan": "^1.5", "phpunit/phpunit": "^8.5 || ^9.5 || ^10.0", "symfony/cache": "^5.0 || ^6.0", "vimeo/psalm": "^4.10 || ^5.9" }, "suggest": { "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures", "doctrine/orm": "For loading ORM fixtures", "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures" }, "config": { "sort-packages": true, "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true } }, "autoload": { "psr-4": { "Doctrine\\Common\\DataFixtures\\": "src" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests" } } } data-fixtures/phpcs.xml.dist 0000644 00000004447 15120025737 0012140 0 ustar 00 <?xml version="1.0"?> <ruleset> <arg name="basepath" value="."/> <arg name="extensions" value="php"/> <arg name="parallel" value="80"/> <arg name="cache" value=".phpcs-cache"/> <arg name="colors" /> <config name="php_version" value="70200"/> <!-- Ignore warnings and show progress of the run --> <arg value="nps"/> <file>src</file> <file>tests</file> <rule ref="Doctrine"> <!-- Traversable type hints often end up as mixed[], so we skip them for now --> <exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification"/> <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification"/> <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification"/> <!-- Will cause BC breaks to method signatures - disabled for now --> <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/> <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/> </rule> <rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses"> <exclude-pattern>tests/*</exclude-pattern> </rule> <rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"> <exclude-pattern>src/AbstractFixture.php</exclude-pattern> <exclude-pattern>src/Executor/AbstractExecutor.php</exclude-pattern> </rule> <rule ref="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"> <exclude-pattern>src/Exception/CircularReferenceException.php</exclude-pattern> </rule> <rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming"> <exclude-pattern>src/DependentFixtureInterface.php</exclude-pattern> <exclude-pattern>src/FixtureInterface.php</exclude-pattern> <exclude-pattern>src/Purger/ORMPurgerInterface.php</exclude-pattern> <exclude-pattern>src/Purger/PurgerInterface.php</exclude-pattern> <exclude-pattern>src/OrderedFixtureInterface.php</exclude-pattern> <exclude-pattern>src/SharedFixtureInterface.php</exclude-pattern> </rule> <rule ref="Squiz.Classes.ClassFileName.NoMatch"> <exclude-pattern>tests/*</exclude-pattern> </rule> </ruleset> data-fixtures/phpstan-baseline.neon 0000644 00000011271 15120025737 0013446 0 ustar 00 parameters: ignoreErrors: - message: "#^Call to method transactional\\(\\) on an unknown class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 1 path: src/Executor/PHPCRExecutor.php - message: "#^Method Doctrine\\\\Common\\\\DataFixtures\\\\Executor\\\\PHPCRExecutor\\:\\:getObjectManager\\(\\) has invalid return type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 1 path: src/Executor/PHPCRExecutor.php - message: "#^Parameter \\$dm of method Doctrine\\\\Common\\\\DataFixtures\\\\Executor\\\\PHPCRExecutor\\:\\:__construct\\(\\) has invalid type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 2 path: src/Executor/PHPCRExecutor.php - message: "#^Property Doctrine\\\\Common\\\\DataFixtures\\\\Executor\\\\PHPCRExecutor\\:\\:\\$dm has unknown class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface as its type\\.$#" count: 1 path: src/Executor/PHPCRExecutor.php - message: "#^Call to an undefined method Doctrine\\\\Common\\\\DataFixtures\\\\FixtureInterface\\:\\:getDependencies\\(\\)\\.$#" count: 1 path: src/Loader.php - message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getReference\\(\\)\\.$#" count: 2 path: src/ProxyReferenceRepository.php - message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getUnitOfWork\\(\\)\\.$#" count: 1 path: src/ProxyReferenceRepository.php - message: "#^Call to method getPhpcrSession\\(\\) on an unknown class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Call to static method purgeWorkspace\\(\\) on an unknown class PHPCR\\\\Util\\\\NodeHelper\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Method Doctrine\\\\Common\\\\DataFixtures\\\\Purger\\\\PHPCRPurger\\:\\:getObjectManager\\(\\) has invalid return type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Parameter \\$dm of method Doctrine\\\\Common\\\\DataFixtures\\\\Purger\\\\PHPCRPurger\\:\\:__construct\\(\\) has invalid type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Parameter \\$dm of method Doctrine\\\\Common\\\\DataFixtures\\\\Purger\\\\PHPCRPurger\\:\\:setDocumentManager\\(\\) has invalid type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManager\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Property Doctrine\\\\Common\\\\DataFixtures\\\\Purger\\\\PHPCRPurger\\:\\:\\$dm \\(Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface\\|null\\) does not accept Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManager\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Property Doctrine\\\\Common\\\\DataFixtures\\\\Purger\\\\PHPCRPurger\\:\\:\\$dm has unknown class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManagerInterface as its type\\.$#" count: 1 path: src/Purger/PHPCRPurger.php - message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\<object\\>\\:\\:\\$name\\.$#" count: 1 path: src/ReferenceRepository.php - message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getReference\\(\\)\\.$#" count: 1 path: src/ReferenceRepository.php - message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getUnitOfWork\\(\\)\\.$#" count: 2 path: src/ReferenceRepository.php - message: "#^Class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManager not found\\.$#" count: 2 path: src/ReferenceRepository.php - message: "#^Method Doctrine\\\\Tests\\\\Common\\\\DataFixtures\\\\FixtureWithUnexistentDependency\\:\\:getDependencies\\(\\) should return array\\<class\\-string\\<Doctrine\\\\Common\\\\DataFixtures\\\\FixtureInterface\\>\\> but returns array\\<int, string\\>\\.$#" count: 1 path: tests/Common/DataFixtures/DependentFixtureTest.php - message: "#^Call to method expects\\(\\) on an unknown class Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManager\\.$#" count: 5 path: tests/Common/DataFixtures/Executor/PHPCRExecutorTest.php - message: "#^Method Doctrine\\\\Tests\\\\Common\\\\DataFixtures\\\\Executor\\\\PHPCRExecutorTest\\:\\:getDocumentManager\\(\\) has invalid return type Doctrine\\\\ODM\\\\PHPCR\\\\DocumentManager\\.$#" count: 1 path: tests/Common/DataFixtures/Executor/PHPCRExecutorTest.php - message: "#^Call to an undefined method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getConnection\\(\\)\\.$#" count: 1 path: tests/Common/DataFixtures/Purger/MongoDBPurgerTest.php data-fixtures/phpstan.neon.dist 0000644 00000000177 15120025737 0012633 0 ustar 00 parameters: phpVersion: 80200 level: 3 paths: - src - tests includes: - phpstan-baseline.neon data-fixtures/psalm-baseline.xml 0000644 00000002044 15120025737 0012744 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="4.22.0@fc2c6ab4d5fa5d644d8617089f012f3bb84b8703"> <file src="src/Executor/PHPCRExecutor.php"> <UndefinedClass occurrences="1"> <code>DocumentManagerInterface</code> </UndefinedClass> <UndefinedDocblockClass occurrences="2"> <code>DocumentManagerInterface</code> <code>DocumentManagerInterface</code> </UndefinedDocblockClass> </file> <file src="src/Purger/PHPCRPurger.php"> <UndefinedClass occurrences="3"> <code>?DocumentManagerInterface</code> <code>DocumentManager</code> <code>NodeHelper</code> </UndefinedClass> <UndefinedDocblockClass occurrences="3"> <code>$this->dm</code> <code>DocumentManagerInterface|null</code> <code>DocumentManagerInterface|null</code> </UndefinedDocblockClass> </file> <file src="src/ReferenceRepository.php"> <UndefinedClass occurrences="2"> <code>PhpcrDocumentManager</code> <code>PhpcrDocumentManager</code> </UndefinedClass> </file> </files> data-fixtures/psalm.xml 0000644 00000001066 15120025737 0011167 0 ustar 00 <?xml version="1.0"?> <psalm phpVersion="7.4" errorLevel="7" findUnusedBaselineEntry="true" findUnusedCode="false" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" errorBaseline="psalm-baseline.xml" > <projectFiles> <directory name="src" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> inflector/docs/en/index.rst 0000644 00000014574 15120025737 0011741 0 ustar 00 Introduction ============ The Doctrine Inflector has methods for inflecting text. The features include pluralization, singularization, converting between camelCase and under_score and capitalizing words. Installation ============ You can install the Inflector with composer: .. code-block:: console $ composer require doctrine/inflector Usage ===== Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using the ``Doctrine\Inflector\InflectorFactory`` class: .. code-block:: php use Doctrine\Inflector\InflectorFactory; $inflector = InflectorFactory::create()->build(); By default it will create an English inflector. If you want to use another language, just pass the language you want to create an inflector for to the ``createForLanguage()`` method: .. code-block:: php use Doctrine\Inflector\InflectorFactory; use Doctrine\Inflector\Language; $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); The supported languages are as follows: - ``Language::ENGLISH`` - ``Language::FRENCH`` - ``Language::NORWEGIAN_BOKMAL`` - ``Language::PORTUGUESE`` - ``Language::SPANISH`` - ``Language::TURKISH`` If you want to manually construct the inflector instead of using a factory, you can do so like this: .. code-block:: php use Doctrine\Inflector\CachedWordInflector; use Doctrine\Inflector\RulesetInflector; use Doctrine\Inflector\Rules\English; $inflector = new Inflector( new CachedWordInflector(new RulesetInflector( English\Rules::getSingularRuleset() )), new CachedWordInflector(new RulesetInflector( English\Rules::getPluralRuleset() )) ); Adding Languages ---------------- If you are interested in adding support for your language, take a look at the other languages defined in the ``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy one of the languages and update the rules for your language. Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. Custom Setup ============ If you want to setup custom singular and plural rules, you can configure these in the factory: .. code-block:: php use Doctrine\Inflector\InflectorFactory; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Transformations; use Doctrine\Inflector\Rules\Word; $inflector = InflectorFactory::create() ->withSingularRules( new Ruleset( new Transformations( new Transformation(new Pattern('/^(bil)er$/i'), '\1'), new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') ), new Patterns(new Pattern('singulars')), new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) ) ) ->withPluralRules( new Ruleset( new Transformations( new Transformation(new Pattern('^(bil)er$'), '\1'), new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') ), new Patterns(new Pattern('noflect'), new Pattern('abtuse')), new Substitutions( new Substitution(new Word('amaze'), new Word('amazable')), new Substitution(new Word('phone'), new Word('phonezes')) ) ) ) ->build(); No operation inflector ---------------------- The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for pluralization and/or singularization. If will simply return the input as output. This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_. .. code-block:: php use Doctrine\Inflector\Inflector; use Doctrine\Inflector\NoopWordInflector; $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); Tableize ======== Converts ``ModelName`` to ``model_name``: .. code-block:: php echo $inflector->tableize('ModelName'); // model_name Classify ======== Converts ``model_name`` to ``ModelName``: .. code-block:: php echo $inflector->classify('model_name'); // ModelName Camelize ======== This method uses `Classify`_ and then converts the first character to lowercase: .. code-block:: php echo $inflector->camelize('model_name'); // modelName Capitalize ========== Takes a string and capitalizes all of the words, like PHP's built-in ``ucwords`` function. This extends that behavior, however, by allowing the word delimiters to be configured, rather than only separating on whitespace. Here is an example: .. code-block:: php $string = 'top-o-the-morning to all_of_you!'; echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! Pluralize ========= Returns a word in plural form. .. code-block:: php echo $inflector->pluralize('browser'); // browsers Singularize =========== Returns a word in singular form. .. code-block:: php echo $inflector->singularize('browsers'); // browser Urlize ====== Generate a URL friendly string from a string of text: .. code-block:: php echo $inflector->urlize('My first blog post'); // my-first-blog-post Unaccent ======== You can unaccent a string of text using the ``unaccent()`` method: .. code-block:: php echo $inflector->unaccent('año'); // ano Legacy API ========== The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. Support for languages other than English is available in the 2.0 API only. Acknowledgements ================ The language rules in this library have been adapted from several different sources, including but not limited to: - `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_ - `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_ - `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_ inflector/lib/Doctrine/Inflector/Rules/English/Inflectible.php 0000644 00000026575 15120025737 0020430 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus'); yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus'); yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus'); yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2'); yield new Transformation(new Pattern('(quiz)zes$'), '\\1'); yield new Transformation(new Pattern('(matr)ices$'), '\1ix'); yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex'); yield new Transformation(new Pattern('^(ox)en'), '\1'); yield new Transformation(new Pattern('(alias)(es)*$'), '\1'); yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o'); yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us'); yield new Transformation(new Pattern('([ftw]ax)es'), '\1'); yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is'); yield new Transformation(new Pattern('(shoe|slave)s$'), '\1'); yield new Transformation(new Pattern('(o)es$'), '\1'); yield new Transformation(new Pattern('ouses$'), 'ouse'); yield new Transformation(new Pattern('([^a])uses$'), '\1us'); yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse'); yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1'); yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie'); yield new Transformation(new Pattern('(s)eries$'), '\1\2eries'); yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y'); yield new Transformation(new Pattern('([lr])ves$'), '\1f'); yield new Transformation(new Pattern('(tive)s$'), '\1'); yield new Transformation(new Pattern('(hive)s$'), '\1'); yield new Transformation(new Pattern('(drive)s$'), '\1'); yield new Transformation(new Pattern('(dive)s$'), '\1'); yield new Transformation(new Pattern('(olive)s$'), '\1'); yield new Transformation(new Pattern('([^fo])ves$'), '\1fe'); yield new Transformation(new Pattern('(^analy)ses$'), '\1sis'); yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis'); yield new Transformation(new Pattern('(tax)a$'), '\1on'); yield new Transformation(new Pattern('(c)riteria$'), '\1riterion'); yield new Transformation(new Pattern('([ti])a$'), '\1um'); yield new Transformation(new Pattern('(p)eople$'), '\1\2erson'); yield new Transformation(new Pattern('(m)en$'), '\1an'); yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild'); yield new Transformation(new Pattern('(f)eet$'), '\1oot'); yield new Transformation(new Pattern('(n)ews$'), '\1\2ews'); yield new Transformation(new Pattern('eaus$'), 'eau'); yield new Transformation(new Pattern('^tights$'), 'tights'); yield new Transformation(new Pattern('^shorts$'), 'shorts'); yield new Transformation(new Pattern('s$'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses'); yield new Transformation(new Pattern('(quiz)$'), '\1zes'); yield new Transformation(new Pattern('^(ox)$'), '\1\2en'); yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice'); yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices'); yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es'); yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies'); yield new Transformation(new Pattern('(hive|gulf)$'), '\1s'); yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves'); yield new Transformation(new Pattern('sis$'), 'ses'); yield new Transformation(new Pattern('([ti])um$'), '\1a'); yield new Transformation(new Pattern('(tax)on$'), '\1a'); yield new Transformation(new Pattern('(c)riterion$'), '\1riteria'); yield new Transformation(new Pattern('(p)erson$'), '\1eople'); yield new Transformation(new Pattern('(m)an$'), '\1en'); yield new Transformation(new Pattern('(c)hild$'), '\1hildren'); yield new Transformation(new Pattern('(f)oot$'), '\1eet'); yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes'); yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i'); yield new Transformation(new Pattern('us$'), 'uses'); yield new Transformation(new Pattern('(alias)$'), '\1es'); yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es'); yield new Transformation(new Pattern('s$'), 's'); yield new Transformation(new Pattern('^$'), ''); yield new Transformation(new Pattern('$'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('atlas'), new Word('atlases')); yield new Substitution(new Word('axe'), new Word('axes')); yield new Substitution(new Word('beef'), new Word('beefs')); yield new Substitution(new Word('blouse'), new Word('blouses')); yield new Substitution(new Word('brother'), new Word('brothers')); yield new Substitution(new Word('cafe'), new Word('cafes')); yield new Substitution(new Word('cave'), new Word('caves')); yield new Substitution(new Word('chateau'), new Word('chateaux')); yield new Substitution(new Word('niveau'), new Word('niveaux')); yield new Substitution(new Word('child'), new Word('children')); yield new Substitution(new Word('canvas'), new Word('canvases')); yield new Substitution(new Word('cookie'), new Word('cookies')); yield new Substitution(new Word('corpus'), new Word('corpuses')); yield new Substitution(new Word('cow'), new Word('cows')); yield new Substitution(new Word('criterion'), new Word('criteria')); yield new Substitution(new Word('curriculum'), new Word('curricula')); yield new Substitution(new Word('demo'), new Word('demos')); yield new Substitution(new Word('domino'), new Word('dominoes')); yield new Substitution(new Word('echo'), new Word('echoes')); yield new Substitution(new Word('foot'), new Word('feet')); yield new Substitution(new Word('fungus'), new Word('fungi')); yield new Substitution(new Word('ganglion'), new Word('ganglions')); yield new Substitution(new Word('gas'), new Word('gases')); yield new Substitution(new Word('genie'), new Word('genies')); yield new Substitution(new Word('genus'), new Word('genera')); yield new Substitution(new Word('goose'), new Word('geese')); yield new Substitution(new Word('graffito'), new Word('graffiti')); yield new Substitution(new Word('hippopotamus'), new Word('hippopotami')); yield new Substitution(new Word('hoof'), new Word('hoofs')); yield new Substitution(new Word('human'), new Word('humans')); yield new Substitution(new Word('iris'), new Word('irises')); yield new Substitution(new Word('larva'), new Word('larvae')); yield new Substitution(new Word('leaf'), new Word('leaves')); yield new Substitution(new Word('lens'), new Word('lenses')); yield new Substitution(new Word('loaf'), new Word('loaves')); yield new Substitution(new Word('man'), new Word('men')); yield new Substitution(new Word('medium'), new Word('media')); yield new Substitution(new Word('memorandum'), new Word('memoranda')); yield new Substitution(new Word('money'), new Word('monies')); yield new Substitution(new Word('mongoose'), new Word('mongooses')); yield new Substitution(new Word('motto'), new Word('mottoes')); yield new Substitution(new Word('move'), new Word('moves')); yield new Substitution(new Word('mythos'), new Word('mythoi')); yield new Substitution(new Word('niche'), new Word('niches')); yield new Substitution(new Word('nucleus'), new Word('nuclei')); yield new Substitution(new Word('numen'), new Word('numina')); yield new Substitution(new Word('occiput'), new Word('occiputs')); yield new Substitution(new Word('octopus'), new Word('octopuses')); yield new Substitution(new Word('opus'), new Word('opuses')); yield new Substitution(new Word('ox'), new Word('oxen')); yield new Substitution(new Word('passerby'), new Word('passersby')); yield new Substitution(new Word('penis'), new Word('penises')); yield new Substitution(new Word('person'), new Word('people')); yield new Substitution(new Word('plateau'), new Word('plateaux')); yield new Substitution(new Word('runner-up'), new Word('runners-up')); yield new Substitution(new Word('safe'), new Word('safes')); yield new Substitution(new Word('sex'), new Word('sexes')); yield new Substitution(new Word('sieve'), new Word('sieves')); yield new Substitution(new Word('soliloquy'), new Word('soliloquies')); yield new Substitution(new Word('son-in-law'), new Word('sons-in-law')); yield new Substitution(new Word('syllabus'), new Word('syllabi')); yield new Substitution(new Word('testis'), new Word('testes')); yield new Substitution(new Word('thief'), new Word('thieves')); yield new Substitution(new Word('tooth'), new Word('teeth')); yield new Substitution(new Word('tornado'), new Word('tornadoes')); yield new Substitution(new Word('trilby'), new Word('trilbys')); yield new Substitution(new Word('turf'), new Word('turfs')); yield new Substitution(new Word('valve'), new Word('valves')); yield new Substitution(new Word('volcano'), new Word('volcanoes')); yield new Substitution(new Word('abuse'), new Word('abuses')); yield new Substitution(new Word('avalanche'), new Word('avalanches')); yield new Substitution(new Word('cache'), new Word('caches')); yield new Substitution(new Word('criterion'), new Word('criteria')); yield new Substitution(new Word('curve'), new Word('curves')); yield new Substitution(new Word('emphasis'), new Word('emphases')); yield new Substitution(new Word('foe'), new Word('foes')); yield new Substitution(new Word('grave'), new Word('graves')); yield new Substitution(new Word('hoax'), new Word('hoaxes')); yield new Substitution(new Word('medium'), new Word('media')); yield new Substitution(new Word('neurosis'), new Word('neuroses')); yield new Substitution(new Word('save'), new Word('saves')); yield new Substitution(new Word('wave'), new Word('waves')); yield new Substitution(new Word('oasis'), new Word('oases')); yield new Substitution(new Word('valve'), new Word('valves')); yield new Substitution(new Word('zombie'), new Word('zombies')); } } inflector/lib/Doctrine/Inflector/Rules/English/InflectorFactory.php 0000644 00000000715 15120025737 0021451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/English/Rules.php 0000644 00000001552 15120025737 0017266 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/English/Uninflected.php 0000644 00000037213 15120025737 0020437 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); yield new Pattern('.*ss'); yield new Pattern('clothes'); yield new Pattern('data'); yield new Pattern('fascia'); yield new Pattern('fuchsia'); yield new Pattern('galleria'); yield new Pattern('mafia'); yield new Pattern('militia'); yield new Pattern('pants'); yield new Pattern('petunia'); yield new Pattern('sepia'); yield new Pattern('trivia'); yield new Pattern('utopia'); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); yield new Pattern('people'); yield new Pattern('trivia'); yield new Pattern('\w+ware$'); yield new Pattern('media'); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('\w+media'); yield new Pattern('absence'); yield new Pattern('access'); yield new Pattern('accommodation'); yield new Pattern('adulthood'); yield new Pattern('advertising'); yield new Pattern('advice'); yield new Pattern('aggression'); yield new Pattern('agriculture'); yield new Pattern('aid'); yield new Pattern('air'); yield new Pattern('aircraft'); yield new Pattern('alcohol'); yield new Pattern('amoyese'); yield new Pattern('anger'); yield new Pattern('applause'); yield new Pattern('arithmetic'); yield new Pattern('art'); yield new Pattern('assistance'); yield new Pattern('atmosphere'); yield new Pattern('attention'); yield new Pattern('audio'); yield new Pattern('bacon'); yield new Pattern('baggage'); yield new Pattern('ballet'); yield new Pattern('beauty'); yield new Pattern('beef'); yield new Pattern('beer'); yield new Pattern('behavior'); yield new Pattern('biology'); yield new Pattern('bison'); yield new Pattern('blood'); yield new Pattern('borghese'); yield new Pattern('botany'); yield new Pattern('bravery'); yield new Pattern('bread'); yield new Pattern('bream'); yield new Pattern('breeches'); yield new Pattern('britches'); yield new Pattern('buffalo'); yield new Pattern('business'); yield new Pattern('butter'); yield new Pattern('cake'); yield new Pattern('cantus'); yield new Pattern('carbon'); yield new Pattern('carp'); yield new Pattern('cash'); yield new Pattern('cattle'); yield new Pattern('chaos'); yield new Pattern('chassis'); yield new Pattern('cheese'); yield new Pattern('chess'); yield new Pattern('childhood'); yield new Pattern('chocolate'); yield new Pattern('clamp'); yield new Pattern('clippers'); yield new Pattern('clothing'); yield new Pattern('coal'); yield new Pattern('cod'); yield new Pattern('coffee'); yield new Pattern('coitus'); yield new Pattern('commerce'); yield new Pattern('compensation'); yield new Pattern('confidence'); yield new Pattern('confusion'); yield new Pattern('congoese'); yield new Pattern('content'); yield new Pattern('contretemps'); yield new Pattern('cookery'); yield new Pattern('coreopsis'); yield new Pattern('corps'); yield new Pattern('corruption'); yield new Pattern('cotton'); yield new Pattern('countryside'); yield new Pattern('courage'); yield new Pattern('crockery'); yield new Pattern('cutlery'); yield new Pattern('damage'); yield new Pattern('dancing'); yield new Pattern('danger'); yield new Pattern('darkness'); yield new Pattern('data'); yield new Pattern('debris'); yield new Pattern('deer'); yield new Pattern('delight'); yield new Pattern('democracy'); yield new Pattern('depression'); yield new Pattern('designs'); yield new Pattern('dessert'); yield new Pattern('determination'); yield new Pattern('diabetes'); yield new Pattern('dignity'); yield new Pattern('dirt'); yield new Pattern('distribution'); yield new Pattern('djinn'); yield new Pattern('driving'); yield new Pattern('dust'); yield new Pattern('duty'); yield new Pattern('earth'); yield new Pattern('economics'); yield new Pattern('education'); yield new Pattern('eland'); yield new Pattern('elk'); yield new Pattern('emoji'); yield new Pattern('employment'); yield new Pattern('engineering'); yield new Pattern('entertainment'); yield new Pattern('enthusiasm'); yield new Pattern('environment'); yield new Pattern('envy'); yield new Pattern('equipment'); yield new Pattern('ethics'); yield new Pattern('evidence'); yield new Pattern('evil'); yield new Pattern('evolution'); yield new Pattern('existence'); yield new Pattern('expense'); yield new Pattern('failure'); yield new Pattern('faith'); yield new Pattern('fame'); yield new Pattern('faroese'); yield new Pattern('fashion'); yield new Pattern('fear'); yield new Pattern('feedback'); yield new Pattern('fiction'); yield new Pattern('finance'); yield new Pattern('fire'); yield new Pattern('fish'); yield new Pattern('flesh'); yield new Pattern('flounder'); yield new Pattern('flour'); yield new Pattern('flu'); yield new Pattern('foochowese'); yield new Pattern('food'); yield new Pattern('forgiveness'); yield new Pattern('freedom'); yield new Pattern('friendship'); yield new Pattern('fruit'); yield new Pattern('fuel'); yield new Pattern('fun'); yield new Pattern('furniture'); yield new Pattern('gallows'); yield new Pattern('garbage'); yield new Pattern('garlic'); yield new Pattern('gasoline'); yield new Pattern('genetics'); yield new Pattern('genevese'); yield new Pattern('genoese'); yield new Pattern('gilbertese'); yield new Pattern('glass'); yield new Pattern('gold'); yield new Pattern('golf'); yield new Pattern('gossip'); yield new Pattern('grammar'); yield new Pattern('grass'); yield new Pattern('gratitude'); yield new Pattern('grief'); yield new Pattern('ground'); yield new Pattern('growth'); yield new Pattern('guilt'); yield new Pattern('gymnastics'); yield new Pattern('hair'); yield new Pattern('happiness'); yield new Pattern('hardware'); yield new Pattern('harm'); yield new Pattern('hate'); yield new Pattern('headquarters'); yield new Pattern('health'); yield new Pattern('height'); yield new Pattern('help'); yield new Pattern('herpes'); yield new Pattern('hijinks'); yield new Pattern('history'); yield new Pattern('homework'); yield new Pattern('honesty'); yield new Pattern('honey'); yield new Pattern('hope'); yield new Pattern('hospitality'); yield new Pattern('hottentotese'); yield new Pattern('housework'); yield new Pattern('humor'); yield new Pattern('hunger'); yield new Pattern('hydrogen'); yield new Pattern('ice'); yield new Pattern('ice cream'); yield new Pattern('imagination'); yield new Pattern('impatience'); yield new Pattern('importance'); yield new Pattern('independence'); yield new Pattern('industry'); yield new Pattern('inflation'); yield new Pattern('information'); yield new Pattern('infrastructure'); yield new Pattern('injustice'); yield new Pattern('innings'); yield new Pattern('innocence'); yield new Pattern('insurance'); yield new Pattern('intelligence'); yield new Pattern('iron'); yield new Pattern('irony'); yield new Pattern('jackanapes'); yield new Pattern('jam'); yield new Pattern('jealousy'); yield new Pattern('jeans'); yield new Pattern('jedi'); yield new Pattern('jewelry'); yield new Pattern('joy'); yield new Pattern('judo'); yield new Pattern('juice'); yield new Pattern('justice'); yield new Pattern('karate'); yield new Pattern('kin'); yield new Pattern('kindness'); yield new Pattern('kiplingese'); yield new Pattern('knowledge'); yield new Pattern('kongoese'); yield new Pattern('labor'); yield new Pattern('lack'); yield new Pattern('land'); yield new Pattern('laughter'); yield new Pattern('lava'); yield new Pattern('leather'); yield new Pattern('leisure'); yield new Pattern('lightning'); yield new Pattern('linguistics'); yield new Pattern('literature'); yield new Pattern('litter'); yield new Pattern('livestock'); yield new Pattern('logic'); yield new Pattern('loneliness'); yield new Pattern('love'); yield new Pattern('lucchese'); yield new Pattern('luck'); yield new Pattern('luggage'); yield new Pattern('machinery'); yield new Pattern('mackerel'); yield new Pattern('magic'); yield new Pattern('mail'); yield new Pattern('maltese'); yield new Pattern('management'); yield new Pattern('mankind'); yield new Pattern('marble'); yield new Pattern('marriage'); yield new Pattern('mathematics'); yield new Pattern('mayonnaise'); yield new Pattern('measles'); yield new Pattern('meat'); yield new Pattern('mercy'); yield new Pattern('metadata'); yield new Pattern('metal'); yield new Pattern('methane'); yield new Pattern('mews'); yield new Pattern('milk'); yield new Pattern('mist'); yield new Pattern('money'); yield new Pattern('moonlight'); yield new Pattern('moose'); yield new Pattern('motherhood'); yield new Pattern('motivation'); yield new Pattern('mud'); yield new Pattern('mumps'); yield new Pattern('music'); yield new Pattern('nankingese'); yield new Pattern('nature'); yield new Pattern('news'); yield new Pattern('nexus'); yield new Pattern('niasese'); yield new Pattern('nitrogen'); yield new Pattern('nutrition'); yield new Pattern('obedience'); yield new Pattern('offspring'); yield new Pattern('oil'); yield new Pattern('oxygen'); yield new Pattern('parking'); yield new Pattern('passion'); yield new Pattern('patience'); yield new Pattern('pekingese'); yield new Pattern('pepper'); yield new Pattern('permission'); yield new Pattern('piedmontese'); yield new Pattern('pincers'); yield new Pattern('pistoiese'); yield new Pattern('plankton'); yield new Pattern('pliers'); yield new Pattern('poetry'); yield new Pattern('pokemon'); yield new Pattern('police'); yield new Pattern('polish'); yield new Pattern('portuguese'); yield new Pattern('proceedings'); yield new Pattern('progress'); yield new Pattern('psychology'); yield new Pattern('quartz'); yield new Pattern('rabies'); yield new Pattern('racism'); yield new Pattern('rain'); yield new Pattern('reliability'); yield new Pattern('relief'); yield new Pattern('research'); yield new Pattern('respect'); yield new Pattern('rhinoceros'); yield new Pattern('rice'); yield new Pattern('safety'); yield new Pattern('salad'); yield new Pattern('salmon'); yield new Pattern('salt'); yield new Pattern('sand'); yield new Pattern('sarawakese'); yield new Pattern('satire'); yield new Pattern('satisfaction'); yield new Pattern('scaffolding'); yield new Pattern('scenery'); yield new Pattern('scissors'); yield new Pattern('sea[- ]bass'); yield new Pattern('seaside'); yield new Pattern('series'); yield new Pattern('sewing'); yield new Pattern('shavese'); yield new Pattern('shears'); yield new Pattern('sheep'); yield new Pattern('shopping'); yield new Pattern('siemens'); yield new Pattern('silk'); yield new Pattern('sms'); yield new Pattern('snow'); yield new Pattern('soap'); yield new Pattern('social media'); yield new Pattern('software'); yield new Pattern('soil'); yield new Pattern('sorrow'); yield new Pattern('spam'); yield new Pattern('species'); yield new Pattern('spite'); yield new Pattern('staff'); yield new Pattern('steam'); yield new Pattern('stream'); yield new Pattern('strength'); yield new Pattern('sugar'); yield new Pattern('swine'); yield new Pattern('talent'); yield new Pattern('tea'); yield new Pattern('technology'); yield new Pattern('temperature'); yield new Pattern('tennis'); yield new Pattern('thirst'); yield new Pattern('tolerance'); yield new Pattern('toothpaste'); yield new Pattern('trade'); yield new Pattern('traffic'); yield new Pattern('transportation'); yield new Pattern('travel'); yield new Pattern('trouble'); yield new Pattern('trousers'); yield new Pattern('trout'); yield new Pattern('trust'); yield new Pattern('tuna'); yield new Pattern('understanding'); yield new Pattern('underwear'); yield new Pattern('unemployment'); yield new Pattern('unity'); yield new Pattern('us'); yield new Pattern('usage'); yield new Pattern('validity'); yield new Pattern('veal'); yield new Pattern('vegetarianism'); yield new Pattern('vegetation'); yield new Pattern('vengeance'); yield new Pattern('vermontese'); yield new Pattern('vinegar'); yield new Pattern('violence'); yield new Pattern('vision'); yield new Pattern('warmth'); yield new Pattern('water'); yield new Pattern('wealth'); yield new Pattern('weather'); yield new Pattern('weight'); yield new Pattern('welfare'); yield new Pattern('wenchowese'); yield new Pattern('wheat'); yield new Pattern('whiting'); yield new Pattern('width'); yield new Pattern('wildebeest'); yield new Pattern('wildlife'); yield new Pattern('wisdom'); yield new Pattern('wood'); yield new Pattern('wool'); yield new Pattern('yeast'); yield new Pattern('yengeese'); yield new Pattern('yoga'); yield new Pattern('youth'); yield new Pattern('zinc'); yield new Pattern('zoology'); } } inflector/lib/Doctrine/Inflector/Rules/French/Inflectible.php 0000644 00000003526 15120025737 0020233 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail'); yield new Transformation(new Pattern('/ails$/'), 'ail'); yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al'); yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1'); yield new Transformation(new Pattern('/s$/'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/(s|x|z)$/'), '\1'); yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux'); yield new Transformation(new Pattern('/ail$/'), 'ails'); yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\1s'); yield new Transformation(new Pattern('/al$/'), 'aux'); yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\1s'); yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\1x'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('monsieur'), new Word('messieurs')); yield new Substitution(new Word('madame'), new Word('mesdames')); yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles')); } } inflector/lib/Doctrine/Inflector/Rules/French/InflectorFactory.php 0000644 00000000714 15120025737 0021264 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/French/Rules.php 0000644 00000001551 15120025737 0017101 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/French/Uninflected.php 0000644 00000001021 15120025737 0020237 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern(''); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Inflectible.php 0000644 00000001657 15120025737 0022110 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/re$/i'), 'r'); yield new Transformation(new Pattern('/er$/i'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/e$/i'), 'er'); yield new Transformation(new Pattern('/r$/i'), 're'); yield new Transformation(new Pattern('/$/'), 'er'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('konto'), new Word('konti')); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/InflectorFactory.php 0000644 00000000725 15120025737 0023140 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Rules.php 0000644 00000001562 15120025737 0020755 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/NorwegianBokmal/Uninflected.php 0000644 00000001144 15120025737 0022117 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('barn'); yield new Pattern('fjell'); yield new Pattern('hus'); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Inflectible.php 0000644 00000013020 15120025737 0021156 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás'); yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês'); yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao'); yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão'); yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1'); yield new Transformation(new Pattern('/sses$/i'), 'sse'); yield new Transformation(new Pattern('/ns$/i'), 'm'); yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il'); yield new Transformation(new Pattern('/uis$/i'), 'ul'); yield new Transformation(new Pattern('/ois$/i'), 'ol'); yield new Transformation(new Pattern('/eis$/i'), 'ei'); yield new Transformation(new Pattern('/éis$/i'), 'el'); yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al'); yield new Transformation(new Pattern('/(r|z)es$/i'), '\1'); yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s'); yield new Transformation(new Pattern('/([^ê])s$/i'), '\1'); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes'); yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos'); yield new Transformation(new Pattern('/ao$/i'), 'oes'); yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães'); yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos'); yield new Transformation(new Pattern('/ão$/i'), 'ões'); yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases'); yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses'); yield new Transformation(new Pattern('/m$/i'), 'ns'); yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is'); yield new Transformation(new Pattern('/ul$/i'), 'uis'); yield new Transformation(new Pattern('/ol$/i'), 'ois'); yield new Transformation(new Pattern('/el$/i'), 'eis'); yield new Transformation(new Pattern('/al$/i'), 'ais'); yield new Transformation(new Pattern('/(z|r)$/i'), '\1es'); yield new Transformation(new Pattern('/(s)$/i'), '\1'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('abdomen'), new Word('abdomens')); yield new Substitution(new Word('alemão'), new Word('alemães')); yield new Substitution(new Word('artesã'), new Word('artesãos')); yield new Substitution(new Word('álcool'), new Word('álcoois')); yield new Substitution(new Word('árvore'), new Word('árvores')); yield new Substitution(new Word('bencão'), new Word('bencãos')); yield new Substitution(new Word('cão'), new Word('cães')); yield new Substitution(new Word('campus'), new Word('campi')); yield new Substitution(new Word('cadáver'), new Word('cadáveres')); yield new Substitution(new Word('capelão'), new Word('capelães')); yield new Substitution(new Word('capitão'), new Word('capitães')); yield new Substitution(new Word('chão'), new Word('chãos')); yield new Substitution(new Word('charlatão'), new Word('charlatães')); yield new Substitution(new Word('cidadão'), new Word('cidadãos')); yield new Substitution(new Word('consul'), new Word('consules')); yield new Substitution(new Word('cristão'), new Word('cristãos')); yield new Substitution(new Word('difícil'), new Word('difíceis')); yield new Substitution(new Word('email'), new Word('emails')); yield new Substitution(new Word('escrivão'), new Word('escrivães')); yield new Substitution(new Word('fóssil'), new Word('fósseis')); yield new Substitution(new Word('gás'), new Word('gases')); yield new Substitution(new Word('germens'), new Word('germen')); yield new Substitution(new Word('grão'), new Word('grãos')); yield new Substitution(new Word('hífen'), new Word('hífens')); yield new Substitution(new Word('irmão'), new Word('irmãos')); yield new Substitution(new Word('liquens'), new Word('liquen')); yield new Substitution(new Word('mal'), new Word('males')); yield new Substitution(new Word('mão'), new Word('mãos')); yield new Substitution(new Word('orfão'), new Word('orfãos')); yield new Substitution(new Word('país'), new Word('países')); yield new Substitution(new Word('pai'), new Word('pais')); yield new Substitution(new Word('pão'), new Word('pães')); yield new Substitution(new Word('projétil'), new Word('projéteis')); yield new Substitution(new Word('réptil'), new Word('répteis')); yield new Substitution(new Word('sacristão'), new Word('sacristães')); yield new Substitution(new Word('sotão'), new Word('sotãos')); yield new Substitution(new Word('tabelião'), new Word('tabeliães')); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/InflectorFactory.php 0000644 00000000720 15120025737 0022216 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Rules.php 0000644 00000001555 15120025737 0020042 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Portuguese/Uninflected.php 0000644 00000001260 15120025737 0021201 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('tórax'); yield new Pattern('tênis'); yield new Pattern('ônibus'); yield new Pattern('lápis'); yield new Pattern('fênix'); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Inflectible.php 0000644 00000003471 15120025737 0020432 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/ereses$/'), 'erés'); yield new Transformation(new Pattern('/iones$/'), 'ión'); yield new Transformation(new Pattern('/ces$/'), 'z'); yield new Transformation(new Pattern('/es$/'), ''); yield new Transformation(new Pattern('/s$/'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\1es'); yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\1es'); yield new Transformation(new Pattern('/í([sn])$/i'), 'i\1es'); yield new Transformation(new Pattern('/é([sn])$/i'), 'e\1es'); yield new Transformation(new Pattern('/á([sn])$/i'), 'a\1es'); yield new Transformation(new Pattern('/z$/i'), 'ces'); yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\1'); yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\1es'); yield new Transformation(new Pattern('/$/'), 's'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('el'), new Word('los')); yield new Substitution(new Word('papá'), new Word('papás')); yield new Substitution(new Word('mamá'), new Word('mamás')); yield new Substitution(new Word('sofá'), new Word('sofás')); yield new Substitution(new Word('mes'), new Word('meses')); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/InflectorFactory.php 0000644 00000000715 15120025737 0021465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Rules.php 0000644 00000001552 15120025737 0017302 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Spanish/Uninflected.php 0000644 00000001147 15120025737 0020450 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('lunes'); yield new Pattern('rompecabezas'); yield new Pattern('crisis'); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Inflectible.php 0000644 00000001756 15120025737 0020462 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Pattern; use Doctrine\Inflector\Rules\Substitution; use Doctrine\Inflector\Rules\Transformation; use Doctrine\Inflector\Rules\Word; class Inflectible { /** @return Transformation[] */ public static function getSingular(): iterable { yield new Transformation(new Pattern('/l[ae]r$/i'), ''); } /** @return Transformation[] */ public static function getPlural(): iterable { yield new Transformation(new Pattern('/([eöiü][^aoıueöiü]{0,6})$/u'), '\1ler'); yield new Transformation(new Pattern('/([aoıu][^aoıueöiü]{0,6})$/u'), '\1lar'); } /** @return Substitution[] */ public static function getIrregular(): iterable { yield new Substitution(new Word('ben'), new Word('biz')); yield new Substitution(new Word('sen'), new Word('siz')); yield new Substitution(new Word('o'), new Word('onlar')); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/InflectorFactory.php 0000644 00000000715 15120025737 0021511 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\GenericLanguageInflectorFactory; use Doctrine\Inflector\Rules\Ruleset; final class InflectorFactory extends GenericLanguageInflectorFactory { protected function getSingularRuleset(): Ruleset { return Rules::getSingularRuleset(); } protected function getPluralRuleset(): Ruleset { return Rules::getPluralRuleset(); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Rules.php 0000644 00000001552 15120025737 0017326 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Patterns; use Doctrine\Inflector\Rules\Ruleset; use Doctrine\Inflector\Rules\Substitutions; use Doctrine\Inflector\Rules\Transformations; final class Rules { public static function getSingularRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getSingular()), new Patterns(...Uninflected::getSingular()), (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() ); } public static function getPluralRuleset(): Ruleset { return new Ruleset( new Transformations(...Inflectible::getPlural()), new Patterns(...Uninflected::getPlural()), new Substitutions(...Inflectible::getIrregular()) ); } } inflector/lib/Doctrine/Inflector/Rules/Turkish/Uninflected.php 0000644 00000001147 15120025737 0020474 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules\Turkish; use Doctrine\Inflector\Rules\Pattern; final class Uninflected { /** @return Pattern[] */ public static function getSingular(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ public static function getPlural(): iterable { yield from self::getDefault(); } /** @return Pattern[] */ private static function getDefault(): iterable { yield new Pattern('lunes'); yield new Pattern('rompecabezas'); yield new Pattern('crisis'); } } inflector/lib/Doctrine/Inflector/Rules/Pattern.php 0000644 00000001424 15120025737 0016216 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use function preg_match; final class Pattern { /** @var string */ private $pattern; /** @var string */ private $regex; public function __construct(string $pattern) { $this->pattern = $pattern; if (isset($this->pattern[0]) && $this->pattern[0] === '/') { $this->regex = $this->pattern; } else { $this->regex = '/' . $this->pattern . '/i'; } } public function getPattern(): string { return $this->pattern; } public function getRegex(): string { return $this->regex; } public function matches(string $word): bool { return preg_match($this->getRegex(), $word) === 1; } } inflector/lib/Doctrine/Inflector/Rules/Patterns.php 0000644 00000001270 15120025737 0016400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use function array_map; use function implode; use function preg_match; class Patterns { /** @var Pattern[] */ private $patterns; /** @var string */ private $regex; public function __construct(Pattern ...$patterns) { $this->patterns = $patterns; $patterns = array_map(static function (Pattern $pattern): string { return $pattern->getPattern(); }, $this->patterns); $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; } public function matches(string $word): bool { return preg_match($this->regex, $word, $regs) === 1; } } inflector/lib/Doctrine/Inflector/Rules/Ruleset.php 0000644 00000001411 15120025737 0016220 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; class Ruleset { /** @var Transformations */ private $regular; /** @var Patterns */ private $uninflected; /** @var Substitutions */ private $irregular; public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular) { $this->regular = $regular; $this->uninflected = $uninflected; $this->irregular = $irregular; } public function getRegular(): Transformations { return $this->regular; } public function getUninflected(): Patterns { return $this->uninflected; } public function getIrregular(): Substitutions { return $this->irregular; } } inflector/lib/Doctrine/Inflector/Rules/Substitution.php 0000644 00000000703 15120025737 0017314 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; final class Substitution { /** @var Word */ private $from; /** @var Word */ private $to; public function __construct(Word $from, Word $to) { $this->from = $from; $this->to = $to; } public function getFrom(): Word { return $this->from; } public function getTo(): Word { return $this->to; } } inflector/lib/Doctrine/Inflector/Rules/Substitutions.php 0000644 00000002534 15120025737 0017503 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; use function strtolower; use function strtoupper; use function substr; class Substitutions implements WordInflector { /** @var Substitution[] */ private $substitutions; public function __construct(Substitution ...$substitutions) { foreach ($substitutions as $substitution) { $this->substitutions[$substitution->getFrom()->getWord()] = $substitution; } } public function getFlippedSubstitutions(): Substitutions { $substitutions = []; foreach ($this->substitutions as $substitution) { $substitutions[] = new Substitution( $substitution->getTo(), $substitution->getFrom() ); } return new Substitutions(...$substitutions); } public function inflect(string $word): string { $lowerWord = strtolower($word); if (isset($this->substitutions[$lowerWord])) { $firstLetterUppercase = $lowerWord[0] !== $word[0]; $toWord = $this->substitutions[$lowerWord]->getTo()->getWord(); if ($firstLetterUppercase) { return strtoupper($toWord[0]) . substr($toWord, 1); } return $toWord; } return $word; } } inflector/lib/Doctrine/Inflector/Rules/Transformation.php 0000644 00000001426 15120025737 0017611 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; use function preg_replace; final class Transformation implements WordInflector { /** @var Pattern */ private $pattern; /** @var string */ private $replacement; public function __construct(Pattern $pattern, string $replacement) { $this->pattern = $pattern; $this->replacement = $replacement; } public function getPattern(): Pattern { return $this->pattern; } public function getReplacement(): string { return $this->replacement; } public function inflect(string $word): string { return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word); } } inflector/lib/Doctrine/Inflector/Rules/Transformations.php 0000644 00000001210 15120025737 0017763 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; use Doctrine\Inflector\WordInflector; class Transformations implements WordInflector { /** @var Transformation[] */ private $transformations; public function __construct(Transformation ...$transformations) { $this->transformations = $transformations; } public function inflect(string $word): string { foreach ($this->transformations as $transformation) { if ($transformation->getPattern()->matches($word)) { return $transformation->inflect($word); } } return $word; } } inflector/lib/Doctrine/Inflector/Rules/Word.php 0000644 00000000446 15120025737 0015517 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector\Rules; class Word { /** @var string */ private $word; public function __construct(string $word) { $this->word = $word; } public function getWord(): string { return $this->word; } } inflector/lib/Doctrine/Inflector/CachedWordInflector.php 0000644 00000000777 15120025737 0017372 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; class CachedWordInflector implements WordInflector { /** @var WordInflector */ private $wordInflector; /** @var string[] */ private $cache = []; public function __construct(WordInflector $wordInflector) { $this->wordInflector = $wordInflector; } public function inflect(string $word): string { return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); } } inflector/lib/Doctrine/Inflector/GenericLanguageInflectorFactory.php 0000644 00000003213 15120025737 0021723 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; use function array_unshift; abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory { /** @var Ruleset[] */ private $singularRulesets = []; /** @var Ruleset[] */ private $pluralRulesets = []; final public function __construct() { $this->singularRulesets[] = $this->getSingularRuleset(); $this->pluralRulesets[] = $this->getPluralRuleset(); } final public function build(): Inflector { return new Inflector( new CachedWordInflector(new RulesetInflector( ...$this->singularRulesets )), new CachedWordInflector(new RulesetInflector( ...$this->pluralRulesets )) ); } final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory { if ($reset) { $this->singularRulesets = []; } if ($singularRules instanceof Ruleset) { array_unshift($this->singularRulesets, $singularRules); } return $this; } final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory { if ($reset) { $this->pluralRulesets = []; } if ($pluralRules instanceof Ruleset) { array_unshift($this->pluralRulesets, $pluralRules); } return $this; } abstract protected function getSingularRuleset(): Ruleset; abstract protected function getPluralRuleset(): Ruleset; } inflector/lib/Doctrine/Inflector/Inflector.php 0000644 00000031060 15120025737 0015433 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use RuntimeException; use function chr; use function function_exists; use function lcfirst; use function mb_strtolower; use function ord; use function preg_match; use function preg_replace; use function sprintf; use function str_replace; use function strlen; use function strtolower; use function strtr; use function trim; use function ucwords; class Inflector { private const ACCENTED_CHARACTERS = [ 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'Ae', 'Æ' => 'Ae', 'Å' => 'Aa', 'æ' => 'a', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'Oe', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'Ue', 'Ý' => 'Y', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'ae', 'å' => 'aa', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'oe', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'ue', 'ý' => 'y', 'ÿ' => 'y', 'Ā' => 'A', 'ā' => 'a', 'Ă' => 'A', 'ă' => 'a', 'Ą' => 'A', 'ą' => 'a', 'Ć' => 'C', 'ć' => 'c', 'Ĉ' => 'C', 'ĉ' => 'c', 'Ċ' => 'C', 'ċ' => 'c', 'Č' => 'C', 'č' => 'c', 'Ď' => 'D', 'ď' => 'd', 'Đ' => 'D', 'đ' => 'd', 'Ē' => 'E', 'ē' => 'e', 'Ĕ' => 'E', 'ĕ' => 'e', 'Ė' => 'E', 'ė' => 'e', 'Ę' => 'E', 'ę' => 'e', 'Ě' => 'E', 'ě' => 'e', 'Ĝ' => 'G', 'ĝ' => 'g', 'Ğ' => 'G', 'ğ' => 'g', 'Ġ' => 'G', 'ġ' => 'g', 'Ģ' => 'G', 'ģ' => 'g', 'Ĥ' => 'H', 'ĥ' => 'h', 'Ħ' => 'H', 'ħ' => 'h', 'Ĩ' => 'I', 'ĩ' => 'i', 'Ī' => 'I', 'ī' => 'i', 'Ĭ' => 'I', 'ĭ' => 'i', 'Į' => 'I', 'į' => 'i', 'İ' => 'I', 'ı' => 'i', 'IJ' => 'IJ', 'ij' => 'ij', 'Ĵ' => 'J', 'ĵ' => 'j', 'Ķ' => 'K', 'ķ' => 'k', 'ĸ' => 'k', 'Ĺ' => 'L', 'ĺ' => 'l', 'Ļ' => 'L', 'ļ' => 'l', 'Ľ' => 'L', 'ľ' => 'l', 'Ŀ' => 'L', 'ŀ' => 'l', 'Ł' => 'L', 'ł' => 'l', 'Ń' => 'N', 'ń' => 'n', 'Ņ' => 'N', 'ņ' => 'n', 'Ň' => 'N', 'ň' => 'n', 'ʼn' => 'N', 'Ŋ' => 'n', 'ŋ' => 'N', 'Ō' => 'O', 'ō' => 'o', 'Ŏ' => 'O', 'ŏ' => 'o', 'Ő' => 'O', 'ő' => 'o', 'Œ' => 'OE', 'œ' => 'oe', 'Ø' => 'O', 'ø' => 'o', 'Ŕ' => 'R', 'ŕ' => 'r', 'Ŗ' => 'R', 'ŗ' => 'r', 'Ř' => 'R', 'ř' => 'r', 'Ś' => 'S', 'ś' => 's', 'Ŝ' => 'S', 'ŝ' => 's', 'Ş' => 'S', 'ş' => 's', 'Š' => 'S', 'š' => 's', 'Ţ' => 'T', 'ţ' => 't', 'Ť' => 'T', 'ť' => 't', 'Ŧ' => 'T', 'ŧ' => 't', 'Ũ' => 'U', 'ũ' => 'u', 'Ū' => 'U', 'ū' => 'u', 'Ŭ' => 'U', 'ŭ' => 'u', 'Ů' => 'U', 'ů' => 'u', 'Ű' => 'U', 'ű' => 'u', 'Ų' => 'U', 'ų' => 'u', 'Ŵ' => 'W', 'ŵ' => 'w', 'Ŷ' => 'Y', 'ŷ' => 'y', 'Ÿ' => 'Y', 'Ź' => 'Z', 'ź' => 'z', 'Ż' => 'Z', 'ż' => 'z', 'Ž' => 'Z', 'ž' => 'z', 'ſ' => 's', '€' => 'E', '£' => '', ]; /** @var WordInflector */ private $singularizer; /** @var WordInflector */ private $pluralizer; public function __construct(WordInflector $singularizer, WordInflector $pluralizer) { $this->singularizer = $singularizer; $this->pluralizer = $pluralizer; } /** * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. */ public function tableize(string $word): string { $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); if ($tableized === null) { throw new RuntimeException(sprintf( 'preg_replace returned null for value "%s"', $word )); } return mb_strtolower($tableized); } /** * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. */ public function classify(string $word): string { return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); } /** * Camelizes a word. This uses the classify() method and turns the first character to lowercase. */ public function camelize(string $word): string { return lcfirst($this->classify($word)); } /** * Uppercases words with configurable delimiters between words. * * Takes a string and capitalizes all of the words, like PHP's built-in * ucwords function. This extends that behavior, however, by allowing the * word delimiters to be configured, rather than only separating on * whitespace. * * Here is an example: * <code> * <?php * $string = 'top-o-the-morning to all_of_you!'; * echo $inflector->capitalize($string); * // Top-O-The-Morning To All_of_you! * * echo $inflector->capitalize($string, '-_ '); * // Top-O-The-Morning To All_Of_You! * ?> * </code> * * @param string $string The string to operate on. * @param string $delimiters A list of word separators. * * @return string The string with all delimiter-separated words capitalized. */ public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string { return ucwords($string, $delimiters); } /** * Checks if the given string seems like it has utf8 characters in it. * * @param string $string The string to check for utf8 characters in. */ public function seemsUtf8(string $string): bool { for ($i = 0; $i < strlen($string); $i++) { if (ord($string[$i]) < 0x80) { continue; // 0bbbbbbb } if ((ord($string[$i]) & 0xE0) === 0xC0) { $n = 1; // 110bbbbb } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { $n = 2; // 1110bbbb } elseif ((ord($string[$i]) & 0xF8) === 0xF0) { $n = 3; // 11110bbb } elseif ((ord($string[$i]) & 0xFC) === 0xF8) { $n = 4; // 111110bb } elseif ((ord($string[$i]) & 0xFE) === 0xFC) { $n = 5; // 1111110b } else { return false; // Does not match any model } for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { return false; } } } return true; } /** * Remove any illegal characters, accents, etc. * * @param string $string String to unaccent * * @return string Unaccented string */ public function unaccent(string $string): string { if (preg_match('/[\x80-\xff]/', $string) === false) { return $string; } if ($this->seemsUtf8($string)) { $string = strtr($string, self::ACCENTED_CHARACTERS); } else { $characters = []; // Assume ISO-8859-1 if not UTF-8 $characters['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) . chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202) . chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210) . chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218) . chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227) . chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235) . chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243) . chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251) . chr(252) . chr(253) . chr(255); $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; $string = strtr($string, $characters['in'], $characters['out']); $doubleChars = []; $doubleChars['in'] = [ chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254), ]; $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); } return $string; } /** * Convert any passed string to a url friendly string. * Converts 'My first blog post' to 'my-first-blog-post' * * @param string $string String to urlize. * * @return string Urlized string. */ public function urlize(string $string): string { // Remove all non url friendly characters with the unaccent function $unaccented = $this->unaccent($string); if (function_exists('mb_strtolower')) { $lowered = mb_strtolower($unaccented); } else { $lowered = strtolower($unaccented); } $replacements = [ '/\W/' => ' ', '/([A-Z]+)([A-Z][a-z])/' => '\1_\2', '/([a-z\d])([A-Z])/' => '\1_\2', '/[^A-Z^a-z^0-9^\/]+/' => '-', ]; $urlized = $lowered; foreach ($replacements as $pattern => $replacement) { $replaced = preg_replace($pattern, $replacement, $urlized); if ($replaced === null) { throw new RuntimeException(sprintf( 'preg_replace returned null for value "%s"', $urlized )); } $urlized = $replaced; } return trim($urlized, '-'); } /** * Returns a word in singular form. * * @param string $word The word in plural form. * * @return string The word in singular form. */ public function singularize(string $word): string { return $this->singularizer->inflect($word); } /** * Returns a word in plural form. * * @param string $word The word in singular form. * * @return string The word in plural form. */ public function pluralize(string $word): string { return $this->pluralizer->inflect($word); } } inflector/lib/Doctrine/Inflector/InflectorFactory.php 0000644 00000002616 15120025737 0016770 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\English; use Doctrine\Inflector\Rules\French; use Doctrine\Inflector\Rules\NorwegianBokmal; use Doctrine\Inflector\Rules\Portuguese; use Doctrine\Inflector\Rules\Spanish; use Doctrine\Inflector\Rules\Turkish; use InvalidArgumentException; use function sprintf; final class InflectorFactory { public static function create(): LanguageInflectorFactory { return self::createForLanguage(Language::ENGLISH); } public static function createForLanguage(string $language): LanguageInflectorFactory { switch ($language) { case Language::ENGLISH: return new English\InflectorFactory(); case Language::FRENCH: return new French\InflectorFactory(); case Language::NORWEGIAN_BOKMAL: return new NorwegianBokmal\InflectorFactory(); case Language::PORTUGUESE: return new Portuguese\InflectorFactory(); case Language::SPANISH: return new Spanish\InflectorFactory(); case Language::TURKISH: return new Turkish\InflectorFactory(); default: throw new InvalidArgumentException(sprintf( 'Language "%s" is not supported.', $language )); } } } inflector/lib/Doctrine/Inflector/Language.php 0000644 00000000656 15120025737 0015240 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; final class Language { public const ENGLISH = 'english'; public const FRENCH = 'french'; public const NORWEGIAN_BOKMAL = 'norwegian-bokmal'; public const PORTUGUESE = 'portuguese'; public const SPANISH = 'spanish'; public const TURKISH = 'turkish'; private function __construct() { } } inflector/lib/Doctrine/Inflector/LanguageInflectorFactory.php 0000644 00000001445 15120025737 0020433 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; interface LanguageInflectorFactory { /** * Applies custom rules for singularisation * * @param bool $reset If true, will unset default inflections for all new rules * * @return $this */ public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self; /** * Applies custom rules for pluralisation * * @param bool $reset If true, will unset default inflections for all new rules * * @return $this */ public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self; /** * Builds the inflector instance with all applicable rules */ public function build(): Inflector; } inflector/lib/Doctrine/Inflector/NoopWordInflector.php 0000644 00000000311 15120025737 0017116 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; class NoopWordInflector implements WordInflector { public function inflect(string $word): string { return $word; } } inflector/lib/Doctrine/Inflector/RulesetInflector.php 0000644 00000002513 15120025737 0017000 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; use Doctrine\Inflector\Rules\Ruleset; use function array_merge; /** * Inflects based on multiple rulesets. * * Rules: * - If the word matches any uninflected word pattern, it is not inflected * - The first ruleset that returns a different value for an irregular word wins * - The first ruleset that returns a different value for a regular word wins * - If none of the above match, the word is left as-is */ class RulesetInflector implements WordInflector { /** @var Ruleset[] */ private $rulesets; public function __construct(Ruleset $ruleset, Ruleset ...$rulesets) { $this->rulesets = array_merge([$ruleset], $rulesets); } public function inflect(string $word): string { if ($word === '') { return ''; } foreach ($this->rulesets as $ruleset) { if ($ruleset->getUninflected()->matches($word)) { return $word; } $inflected = $ruleset->getIrregular()->inflect($word); if ($inflected !== $word) { return $inflected; } $inflected = $ruleset->getRegular()->inflect($word); if ($inflected !== $word) { return $inflected; } } return $word; } } inflector/lib/Doctrine/Inflector/WordInflector.php 0000644 00000000217 15120025737 0016267 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Inflector; interface WordInflector { public function inflect(string $word): string; } inflector/LICENSE 0000644 00000002051 15120025737 0007536 0 ustar 00 Copyright (c) 2006-2015 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. inflector/README.md 0000644 00000001015 15120025737 0010007 0 ustar 00 # Doctrine Inflector Doctrine Inflector is a small library that can perform string manipulations with regard to uppercase/lowercase and singular/plural forms of words. [](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x) [](https://codecov.io/gh/doctrine/inflector/branch/2.0.x) inflector/composer.json 0000644 00000003020 15120025737 0011250 0 ustar 00 { "name": "doctrine/inflector", "type": "library", "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", "keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"], "homepage": "https://www.doctrine-project.org/projects/inflector.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} ], "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", "vimeo/psalm": "^4.25 || ^5.4" }, "autoload": { "psr-4": { "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } doctrine-migrations-bundle/Collector/MigrationsCollector.php 0000644 00000005227 15120025737 0020426 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\Collector; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; class MigrationsCollector extends DataCollector { /** @var DependencyFactory */ private $dependencyFactory; /** @var MigrationsFlattener */ private $flattener; public function __construct(DependencyFactory $dependencyFactory, MigrationsFlattener $migrationsFlattener) { $this->dependencyFactory = $dependencyFactory; $this->flattener = $migrationsFlattener; } public function collect(Request $request, Response $response, \Throwable $exception = null) { $metadataStorage = $this->dependencyFactory->getMetadataStorage(); $planCalculator = $this->dependencyFactory->getMigrationPlanCalculator(); $statusCalculator = $this->dependencyFactory->getMigrationStatusCalculator(); $executedMigrations = $metadataStorage->getExecutedMigrations(); $availableMigrations = $planCalculator->getMigrations(); $this->data['available_migrations'] = $this->flattener->flattenAvailableMigrations($availableMigrations, $executedMigrations); $this->data['executed_migrations'] = $this->flattener->flattenExecutedMigrations($executedMigrations, $availableMigrations); $this->data['new_migrations'] = $this->flattener->flattenAvailableMigrations($statusCalculator->getNewMigrations()); $this->data['unavailable_migrations'] = $this->flattener->flattenExecutedMigrations($statusCalculator->getExecutedUnavailableMigrations()); $this->data['storage'] = get_class($metadataStorage); $configuration = $this->dependencyFactory->getConfiguration(); $storage = $configuration->getMetadataStorageConfiguration(); if ($storage instanceof TableMetadataStorageConfiguration) { $this->data['table'] = $storage->getTableName(); $this->data['column'] = $storage->getVersionColumnName(); } $connection = $this->dependencyFactory->getConnection(); $this->data['driver'] = get_class($connection->getDriver()); $this->data['name'] = $connection->getDatabase(); $this->data['namespaces'] = $configuration->getMigrationDirectories(); } public function getName() { return 'doctrine_migrations'; } public function getData() { return $this->data; } public function reset() { $this->data = []; } } doctrine-migrations-bundle/Collector/MigrationsFlattener.php 0000644 00000004712 15120025737 0020422 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\Collector; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; class MigrationsFlattener { public function flattenAvailableMigrations(AvailableMigrationsList $migrationsList, ?ExecutedMigrationsList $executedMigrations = null): array { return array_map(static function (AvailableMigration $migration) use ($executedMigrations) { $executedMigration = $executedMigrations && $executedMigrations->hasMigration($migration->getVersion()) ? $executedMigrations->getMigration($migration->getVersion()) : null; return [ 'version' => (string)$migration->getVersion(), 'is_new' => !$executedMigration, 'is_unavailable' => false, 'description' => $migration->getMigration()->getDescription(), 'executed_at' => $executedMigration ? $executedMigration->getExecutedAt() : null, 'execution_time' => $executedMigration ? $executedMigration->getExecutionTime() : null, 'file' => (new \ReflectionClass($migration->getMigration()))->getFileName(), ]; }, $migrationsList->getItems()); } public function flattenExecutedMigrations(ExecutedMigrationsList $migrationsList, ?AvailableMigrationsList $availableMigrations = null): array { return array_map(static function (ExecutedMigration $migration) use ($availableMigrations) { $availableMigration = $availableMigrations && $availableMigrations->hasMigration($migration->getVersion()) ? $availableMigrations->getMigration($migration->getVersion())->getMigration() : null; return [ 'version' => (string)$migration->getVersion(), 'is_new' => false, 'is_unavailable' => !$availableMigration, 'description' => $availableMigration ? $availableMigration->getDescription() : null, 'executed_at' => $migration->getExecutedAt(), 'execution_time' => $migration->getExecutionTime(), 'file' => $availableMigration ? (new \ReflectionClass($availableMigration))->getFileName() : null, ]; }, $migrationsList->getItems()); } } doctrine-migrations-bundle/DependencyInjection/CompilerPass/ConfigureDependencyFactoryPass.php 0000644 00000011451 15120025737 0027132 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass; use Doctrine\Migrations\DependencyFactory; use InvalidArgumentException; use RuntimeException; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use function array_keys; use function assert; use function count; use function implode; use function is_array; use function is_string; use function sprintf; class ConfigureDependencyFactoryPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if (! $container->has('doctrine')) { throw new RuntimeException('DoctrineMigrationsBundle requires DoctrineBundle to be enabled.'); } $diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory'); $preferredConnection = $container->getParameter('doctrine.migrations.preferred_connection'); assert(is_string($preferredConnection) || $preferredConnection === null); // explicitly use configured connection if ($preferredConnection !== null) { $this->validatePreferredConnection($container, $preferredConnection); $loaderDefinition = $container->getDefinition('doctrine.migrations.connection_registry_loader'); $loaderDefinition->setArgument(1, $preferredConnection); $diDefinition->setFactory([DependencyFactory::class, 'fromConnection']); $diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader')); return; } $preferredEm = $container->getParameter('doctrine.migrations.preferred_em'); assert(is_string($preferredEm) || $preferredEm === null); // explicitly use configured entity manager if ($preferredEm !== null) { $this->validatePreferredEm($container, $preferredEm); $loaderDefinition = $container->getDefinition('doctrine.migrations.entity_manager_registry_loader'); $loaderDefinition->setArgument(1, $preferredEm); $diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']); $diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader')); return; } // try to use any/default entity manager if ( $container->hasParameter('doctrine.entity_managers') && is_array($container->getParameter('doctrine.entity_managers')) && count($container->getParameter('doctrine.entity_managers')) > 0 ) { $diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']); $diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader')); return; } // fallback on any/default connection $diDefinition->setFactory([DependencyFactory::class, 'fromConnection']); $diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader')); } private function validatePreferredConnection(ContainerBuilder $container, string $preferredConnection): void { /** * @var array<string, string> $allowedConnections */ $allowedConnections = $container->getParameter('doctrine.connections'); if (! isset($allowedConnections[$preferredConnection])) { throw new InvalidArgumentException(sprintf( 'The "%s" connection is not defined. Did you mean one of the following: %s', $preferredConnection, implode(', ', array_keys($allowedConnections)) )); } } private function validatePreferredEm(ContainerBuilder $container, string $preferredEm): void { if ( ! $container->hasParameter('doctrine.entity_managers') || ! is_array($container->getParameter('doctrine.entity_managers')) || count($container->getParameter('doctrine.entity_managers')) === 0 ) { throw new InvalidArgumentException(sprintf( 'The "%s" entity manager is not defined. It seems that you do not have configured any entity manager in the DoctrineBundle.', $preferredEm )); } /** * @var array<string, string> $allowedEms */ $allowedEms = $container->getParameter('doctrine.entity_managers'); if (! isset($allowedEms[$preferredEm])) { throw new InvalidArgumentException(sprintf( 'The "%s" entity manager is not defined. Did you mean one of the following: %s', $preferredEm, implode(', ', array_keys($allowedEms)) )); } } } doctrine-migrations-bundle/DependencyInjection/Configuration.php 0000644 00000017267 15120025737 0021254 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection; use ReflectionClass; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use function array_filter; use function array_keys; use function constant; use function count; use function in_array; use function is_string; use function method_exists; use function strlen; use function strpos; use function strtoupper; use function substr; /** * DoctrineMigrationsExtension configuration structure. */ class Configuration implements ConfigurationInterface { /** * Generates the configuration tree. * * @return TreeBuilder The config tree builder */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('doctrine_migrations'); if (method_exists($treeBuilder, 'getRootNode')) { $rootNode = $treeBuilder->getRootNode(); } else { // BC layer for symfony/config 4.1 and older $rootNode = $treeBuilder->root('doctrine_migrations', 'array'); } $organizeMigrationModes = $this->getOrganizeMigrationsModes(); $rootNode ->fixXmlConfig('migration', 'migrations') ->fixXmlConfig('migrations_path', 'migrations_paths') ->children() ->arrayNode('migrations_paths') ->info('A list of namespace/path pairs where to look for migrations.') ->defaultValue([]) ->useAttributeAsKey('namespace') ->prototype('scalar')->end() ->end() ->arrayNode('services') ->info('A set of services to pass to the underlying doctrine/migrations library, allowing to change its behaviour.') ->useAttributeAsKey('service') ->defaultValue([]) ->validate() ->ifTrue(static function ($v): bool { return count(array_filter(array_keys($v), static function (string $doctrineService): bool { return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0; })) !== 0; }) ->thenInvalid('Valid services for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.') ->end() ->prototype('scalar')->end() ->end() ->arrayNode('factories') ->info('A set of callables to pass to the underlying doctrine/migrations library as services, allowing to change its behaviour.') ->useAttributeAsKey('factory') ->defaultValue([]) ->validate() ->ifTrue(static function ($v): bool { return count(array_filter(array_keys($v), static function (string $doctrineService): bool { return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0; })) !== 0; }) ->thenInvalid('Valid callables for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.') ->end() ->prototype('scalar')->end() ->end() ->arrayNode('storage') ->addDefaultsIfNotSet() ->info('Storage to use for migration status metadata.') ->children() ->arrayNode('table_storage') ->addDefaultsIfNotSet() ->info('The default metadata storage, implemented as a table in the database.') ->children() ->scalarNode('table_name')->defaultValue(null)->cannotBeEmpty()->end() ->scalarNode('version_column_name')->defaultValue(null)->end() ->scalarNode('version_column_length')->defaultValue(null)->end() ->scalarNode('executed_at_column_name')->defaultValue(null)->end() ->scalarNode('execution_time_column_name')->defaultValue(null)->end() ->end() ->end() ->end() ->end() ->arrayNode('migrations') ->info('A list of migrations to load in addition to the one discovered via "migrations_paths".') ->prototype('scalar')->end() ->defaultValue([]) ->end() ->scalarNode('connection') ->info('Connection name to use for the migrations database.') ->defaultValue(null) ->end() ->scalarNode('em') ->info('Entity manager name to use for the migrations database (available when doctrine/orm is installed).') ->defaultValue(null) ->end() ->scalarNode('all_or_nothing') ->info('Run all migrations in a transaction.') ->defaultValue(false) ->end() ->scalarNode('check_database_platform') ->info('Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on.') ->defaultValue(true) ->end() ->scalarNode('custom_template') ->info('Custom template path for generated migration classes.') ->defaultValue(null) ->end() ->scalarNode('organize_migrations') ->defaultValue(false) ->info('Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false') ->validate() ->ifTrue(static function ($v) use ($organizeMigrationModes): bool { if ($v === false) { return false; } return ! is_string($v) || ! in_array(strtoupper($v), $organizeMigrationModes, true); }) ->thenInvalid('Invalid organize migrations mode value %s') ->end() ->validate() ->ifString() ->then(static function ($v) { return constant('Doctrine\Migrations\Configuration\Configuration::VERSIONS_ORGANIZATION_' . strtoupper($v)); }) ->end() ->end() ->booleanNode('enable_profiler') ->info('Use profiler to calculate and visualize migration status.') ->defaultFalse() ->end() ->end(); return $treeBuilder; } /** * Find organize migrations modes for their names * * @return string[] */ private function getOrganizeMigrationsModes(): array { $constPrefix = 'VERSIONS_ORGANIZATION_'; $prefixLen = strlen($constPrefix); $refClass = new ReflectionClass('Doctrine\Migrations\Configuration\Configuration'); $constsArray = $refClass->getConstants(); $namesArray = []; foreach ($constsArray as $key => $value) { if (strpos($key, $constPrefix) !== 0) { continue; } $namesArray[] = substr($key, $prefixLen); } return $namesArray; } } doctrine-migrations-bundle/DependencyInjection/DoctrineMigrationsExtension.php 0000644 00000017412 15120025737 0024136 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection; use Doctrine\Bundle\MigrationsBundle\Collector\MigrationsCollector; use Doctrine\Bundle\MigrationsBundle\Collector\MigrationsFlattener; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\Migrations\Version\MigrationFactory; use InvalidArgumentException; use RuntimeException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use function array_keys; use function assert; use function explode; use function implode; use function is_array; use function sprintf; use function strlen; use function substr; /** * DoctrineMigrationsExtension. */ class DoctrineMigrationsExtension extends Extension { /** * Responds to the migrations configuration parameter. * * @param mixed[][] $configs * * @psalm-param array<string, array<string, array<string, string>|string>>> $configs */ public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $locator = new FileLocator(__DIR__ . '/../Resources/config/'); $loader = new XmlFileLoader($container, $locator); $loader->load('services.xml'); $configurationDefinition = $container->getDefinition('doctrine.migrations.configuration'); foreach ($config['migrations_paths'] as $ns => $path) { $path = $this->checkIfBundleRelativePath($path, $container); $configurationDefinition->addMethodCall('addMigrationsDirectory', [$ns, $path]); } foreach ($config['migrations'] as $migrationClass) { $configurationDefinition->addMethodCall('addMigrationClass', [$migrationClass]); } if ($config['organize_migrations'] !== false) { $configurationDefinition->addMethodCall('setMigrationOrganization', [$config['organize_migrations']]); } if ($config['custom_template'] !== null) { $configurationDefinition->addMethodCall('setCustomTemplate', [$config['custom_template']]); } $configurationDefinition->addMethodCall('setAllOrNothing', [$config['all_or_nothing']]); $configurationDefinition->addMethodCall('setCheckDatabasePlatform', [$config['check_database_platform']]); if ($config['enable_profiler']) { $this->registerCollector($container); } $diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory'); if (! isset($config['services'][MigrationFactory::class])) { $config['services'][MigrationFactory::class] = 'doctrine.migrations.migrations_factory'; } foreach ($config['services'] as $doctrineId => $symfonyId) { $diDefinition->addMethodCall('setDefinition', [$doctrineId, new ServiceClosureArgument(new Reference($symfonyId))]); } foreach ($config['factories'] as $doctrineId => $symfonyId) { $diDefinition->addMethodCall('setDefinition', [$doctrineId, new Reference($symfonyId)]); } if (! isset($config['services'][MetadataStorage::class])) { $storageConfiguration = $config['storage']['table_storage']; $storageDefinition = new Definition(TableMetadataStorageConfiguration::class); $container->setDefinition('doctrine.migrations.storage.table_storage', $storageDefinition); $container->setAlias('doctrine.migrations.metadata_storage', 'doctrine.migrations.storage.table_storage'); if ($storageConfiguration['table_name'] !== null) { $storageDefinition->addMethodCall('setTableName', [$storageConfiguration['table_name']]); } if ($storageConfiguration['version_column_name'] !== null) { $storageDefinition->addMethodCall('setVersionColumnName', [$storageConfiguration['version_column_name']]); } if ($storageConfiguration['version_column_length'] !== null) { $storageDefinition->addMethodCall('setVersionColumnLength', [$storageConfiguration['version_column_length']]); } if ($storageConfiguration['executed_at_column_name'] !== null) { $storageDefinition->addMethodCall('setExecutedAtColumnName', [$storageConfiguration['executed_at_column_name']]); } if ($storageConfiguration['execution_time_column_name'] !== null) { $storageDefinition->addMethodCall('setExecutionTimeColumnName', [$storageConfiguration['execution_time_column_name']]); } $configurationDefinition->addMethodCall('setMetadataStorageConfiguration', [new Reference('doctrine.migrations.storage.table_storage')]); } if ($config['em'] !== null && $config['connection'] !== null) { throw new InvalidArgumentException( 'You cannot specify both "connection" and "em" in the DoctrineMigrationsBundle configurations.' ); } $container->setParameter('doctrine.migrations.preferred_em', $config['em']); $container->setParameter('doctrine.migrations.preferred_connection', $config['connection']); } private function checkIfBundleRelativePath(string $path, ContainerBuilder $container): string { if (isset($path[0]) && $path[0] === '@') { $pathParts = explode('/', $path); $bundleName = substr($pathParts[0], 1); $bundlePath = $this->getBundlePath($bundleName, $container); return $bundlePath . substr($path, strlen('@' . $bundleName)); } return $path; } private function getBundlePath(string $bundleName, ContainerBuilder $container): string { $bundleMetadata = $container->getParameter('kernel.bundles_metadata'); assert(is_array($bundleMetadata)); if (! isset($bundleMetadata[$bundleName])) { throw new RuntimeException(sprintf( 'The bundle "%s" has not been registered, available bundles: %s', $bundleName, implode(', ', array_keys($bundleMetadata)) )); } return $bundleMetadata[$bundleName]['path']; } private function registerCollector(ContainerBuilder $container): void { $flattenerDefinition = new Definition(MigrationsFlattener::class); $container->setDefinition('doctrine_migrations.migrations_flattener', $flattenerDefinition); $collectorDefinition = new Definition(MigrationsCollector::class, [ new Reference('doctrine.migrations.dependency_factory'), new Reference('doctrine_migrations.migrations_flattener'), ]); $collectorDefinition ->addTag('data_collector', [ 'template' => '@DoctrineMigrations/Collector/migrations.html.twig', 'id' => 'doctrine_migrations', 'priority' => '249', ]); $container->setDefinition('doctrine_migrations.migrations_collector', $collectorDefinition); } /** * Returns the base path for the XSD files. * * @return string The XSD base path */ public function getXsdValidationBasePath(): string { return __DIR__ . '/../Resources/config/schema'; } public function getNamespace(): string { return 'http://symfony.com/schema/dic/doctrine/migrations/3.0'; } } doctrine-migrations-bundle/MigrationsFactory/ContainerAwareMigrationFactory.php 0000644 00000002060 15120025737 0024255 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Bundle\MigrationsBundle\MigrationsFactory; use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\Version\MigrationFactory; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; class ContainerAwareMigrationFactory implements MigrationFactory { /** * @var ContainerInterface */ private $container; /** * @var MigrationFactory */ private $migrationFactory; public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container) { $this->container = $container; $this->migrationFactory = $migrationFactory; } public function createVersion(string $migrationClassName): AbstractMigration { $migration = $this->migrationFactory->createVersion($migrationClassName); if ($migration instanceof ContainerAwareInterface) { $migration->setContainer($this->container); } return $migration; } } doctrine-migrations-bundle/Resources/config/schema/doctrine_migrations-3.0.xsd 0000644 00000006462 15120025737 0023546 0 ustar 00 <?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns="http://symfony.com/schema/dic/doctrine/migrations/3.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://symfony.com/schema/dic/doctrine/migrations/3.0" elementFormDefault="qualified" > <xsd:element name="config"> <xsd:complexType> <xsd:sequence> <xsd:element name="migrations-path" maxOccurs="unbounded"> <xsd:complexType> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="namespace" type="xsd:string"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:element name="services" maxOccurs="unbounded" minOccurs="0"> <xsd:complexType> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="service" type="xsd:string"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:element name="migration" type="xsd:string" maxOccurs="unbounded" minOccurs="0"/> <xsd:element name="storage" minOccurs="0"> <xsd:complexType> <xsd:sequence> <xsd:element name="table-storage" minOccurs="0"> <xsd:complexType> <xsd:attribute name="table-name" type="xsd:string"/> <xsd:attribute name="version-column-name" type="xsd:string"/> <xsd:attribute name="version-column-length" type="xsd:positiveInteger"/> <xsd:attribute name="executed-at-column-name" type="xsd:string"/> <xsd:attribute name="execution-time-column-name" type="xsd:string"/> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> <xsd:attribute name="name" type="xsd:string"/> <xsd:attribute name="em" type="xsd:string"/> <xsd:attribute name="connection" type="xsd:string"/> <xsd:attribute name="sorter" type="xsd:string"/> <xsd:attribute name="all_or_nothing" type="xsd:boolean"/> <xsd:attribute name="check_database_platform" type="xsd:boolean"/> <xsd:attribute name="custom_template" type="xsd:string"/> <xsd:attribute name="organize-migrations"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="BY_YEAR"/> <xsd:enumeration value="BY_YEAR_AND_MONTH"/> <xsd:enumeration value="false"/> </xsd:restriction> </xsd:simpleType> </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:schema> doctrine-migrations-bundle/Resources/config/services.xml 0000644 00000017076 15120025737 0017575 0 ustar 00 <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="doctrine.migrations.dependency_factory" class="Doctrine\Migrations\DependencyFactory" public="false"> <factory></factory> <argument type="service" id="doctrine.migrations.configuration_loader"/> <argument></argument> <argument type="service" id="logger" on-invalid="null"></argument> </service> <service id="doctrine.migrations.configuration_loader" class="Doctrine\Migrations\Configuration\Migration\ExistingConfiguration" public="false"> <argument type="service" id="doctrine.migrations.configuration"/> </service> <service id="doctrine.migrations.connection_loader" class="Doctrine\Migrations\Configuration\Connection\ExistingConnection" public="false"> </service> <service id="doctrine.migrations.em_loader" class="Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager" public="false"> </service> <service id="doctrine.migrations.entity_manager_registry_loader" class="Doctrine\Migrations\Configuration\EntityManager\ManagerRegistryEntityManager" public="false"> <factory class="Doctrine\Migrations\Configuration\EntityManager\ManagerRegistryEntityManager" method="withSimpleDefault"/> <argument type="service" id="doctrine"/> </service> <service id="doctrine.migrations.connection_registry_loader" class="Doctrine\Migrations\Configuration\Connection\ConnectionRegistryConnection" public="false"> <factory class="Doctrine\Migrations\Configuration\Connection\ConnectionRegistryConnection" method="withSimpleDefault"/> <argument type="service" id="doctrine"/> </service> <service id="doctrine.migrations.configuration" class="Doctrine\Migrations\Configuration\Configuration" public="false"> </service> <service id="doctrine.migrations.migrations_factory" class="Doctrine\Migrations\Version\MigrationFactory"> <factory service="doctrine.migrations.dependency_factory" method="getMigrationFactory"/> </service> <service id="doctrine.migrations.container_aware_migrations_factory" class="Doctrine\Bundle\MigrationsBundle\MigrationsFactory\ContainerAwareMigrationFactory" decorates="doctrine.migrations.migrations_factory" > <argument id="doctrine.migrations.container_aware_migrations_factory.inner" type="service"/> <argument id="service_container" type="service"/> </service> <service id="doctrine_migrations.diff_command" class="Doctrine\Migrations\Tools\Console\Command\DiffCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:diff</argument> <tag name="console.command" command="doctrine:migrations:diff" /> </service> <service id="doctrine_migrations.sync_metadata_command" class="Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:sync-metadata-storage</argument> <tag name="console.command" command="doctrine:migrations:sync-metadata-storage" /> </service> <service id="doctrine_migrations.versions_command" class="Doctrine\Migrations\Tools\Console\Command\ListCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:versions</argument> <tag name="console.command" command="doctrine:migrations:list" /> </service> <service id="doctrine_migrations.current_command" class="Doctrine\Migrations\Tools\Console\Command\CurrentCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:current</argument> <tag name="console.command" command="doctrine:migrations:current"/> </service> <service id="doctrine_migrations.dump_schema_command" class="Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:dump-schema</argument> <tag name="console.command" command="doctrine:migrations:dump-schema"/> </service> <service id="doctrine_migrations.execute_command" class="Doctrine\Migrations\Tools\Console\Command\ExecuteCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:execute</argument> <tag name="console.command" command="doctrine:migrations:execute"/> </service> <service id="doctrine_migrations.generate_command" class="Doctrine\Migrations\Tools\Console\Command\GenerateCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:generate</argument> <tag name="console.command" command="doctrine:migrations:generate"/> </service> <service id="doctrine_migrations.latest_command" class="Doctrine\Migrations\Tools\Console\Command\LatestCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:latest</argument> <tag name="console.command" command="doctrine:migrations:latest"/> </service> <service id="doctrine_migrations.migrate_command" class="Doctrine\Migrations\Tools\Console\Command\MigrateCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:migrate</argument> <tag name="console.command" command="doctrine:migrations:migrate" /> </service> <service id="doctrine_migrations.rollup_command" class="Doctrine\Migrations\Tools\Console\Command\RollupCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:rollup</argument> <tag name="console.command" command="doctrine:migrations:rollup" /> </service> <service id="doctrine_migrations.status_command" class="Doctrine\Migrations\Tools\Console\Command\StatusCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:status</argument> <tag name="console.command" command="doctrine:migrations:status" /> </service> <service id="doctrine_migrations.up_to_date_command" class="Doctrine\Migrations\Tools\Console\Command\UpToDateCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:up-to-date</argument> <tag name="console.command" command="doctrine:migrations:up-to-date" /> </service> <service id="doctrine_migrations.version_command" class="Doctrine\Migrations\Tools\Console\Command\VersionCommand"> <argument type="service" id="doctrine.migrations.dependency_factory"/> <argument>doctrine:migrations:version</argument> <tag name="console.command" command="doctrine:migrations:version" /> </service> </services> </container> doctrine-migrations-bundle/Resources/doc/index.rst 0000644 00000040307 15120025737 0016362 0 ustar 00 DoctrineMigrationsBundle ======================== Database migrations are a way to safely update your database schema both locally and on production. Instead of running the ``doctrine:schema:update`` command or applying the database changes manually with SQL statements, migrations allow to replicate the changes in your database schema in a safe manner. Migrations are available in Symfony applications via the `DoctrineMigrationsBundle`_, which uses the external `Doctrine Database Migrations`_ library. Read the `documentation`_ of that library if you need a general introduction about migrations. Installation ------------ Run this command in your terminal: .. code-block:: terminal $ composer require doctrine/doctrine-migrations-bundle "^3.0" If you don't use `Symfony Flex`_, you must enable the bundle manually in the application: .. code-block:: php // config/bundles.php // in older Symfony apps, enable the bundle in app/AppKernel.php return [ // ... Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], ]; Configuration ------------- If you use Symfony Flex, the ``doctrine_migrations.yaml`` config file is created automatically. Otherwise, create the following file and configure it for your application: .. code-block:: yaml # config/packages/doctrine_migrations.yaml doctrine_migrations: # List of namespace/path pairs to search for migrations, at least one required migrations_paths: 'App\Migrations': '%kernel.project_dir%/src/App' 'AnotherApp\Migrations': '/path/to/other/migrations' 'SomeBundle\Migrations': '@SomeBundle/Migrations' # List of additional migration classes to be loaded, optional migrations: - 'App\Migrations\Version123' - 'App\Migrations\Version123' # Connection to use for the migrations connection: default # Entity manager to use for migrations. This overrides the "connection" setting. em: default storage: # Default (SQL table) metadata storage configuration table_storage: table_name: 'doctrine_migration_versions' version_column_name: 'version' version_column_length: 1024 executed_at_column_name: 'executed_at' # Possible values: "BY_YEAR", "BY_YEAR_AND_MONTH", false organize_migrations: false # Path to your custom migrations template custom_template: ~ # Run all migrations in a transaction. all_or_nothing: false # Adds an extra check in the generated migrations to ensure that is executed on the same database type. check_database_platform: true services: # Custom migration sorting service id 'Doctrine\Migrations\Version\Comparator': ~ # Custom migration classes factory 'Doctrine\Migrations\Version\MigrationFactory': ~ factories: # Custom migration sorting service id via callables (MyCallableFactory must be a callable) 'Doctrine\Migrations\Version\Comparator': 'MyCallableFactory' - The ``services`` node allows you to provide custom services to the underlying ``DependencyFactory`` part of ``doctrine/migrations``. - The node ``factories`` is similar to ``services``, with the difference that it accepts only callables. The provided callable must return the service to be passed to the ``DependencyFactory``. The callable will receive as first argument the ``DependencyFactory`` itself, allowing you to fetch other dependencies from the factory while instantiating your custom dependencies. Usage ----- All of the migrations functionality is contained in a few console commands: .. code-block:: terminal doctrine doctrine:migrations:current [current] Outputs the current version. doctrine:migrations:diff [diff] Generate a migration by comparing your current database to your mapping information. doctrine:migrations:dump-schema [dump-schema] Dump the schema for your database to a migration. doctrine:migrations:execute [execute] Execute a single migration version up or down manually. doctrine:migrations:generate [generate] Generate a blank migration class. doctrine:migrations:latest [latest] Outputs the latest version number doctrine:migrations:migrate [migrate] Execute a migration to a specified version or the latest available version. doctrine:migrations:rollup [rollup] Roll migrations up by deleting all tracked versions and inserting the one version that exists. doctrine:migrations:status [status] View the status of a set of migrations. doctrine:migrations:up-to-date [up-to-date] Tells you if your schema is up-to-date. doctrine:migrations:version [version] Manually add and delete migration versions from the version table. doctrine:migrations:sync-metadata-storage [sync-metadata-storage] Ensures that the metadata storage is at the latest version. doctrine:migrations:list [list-migrations] Display a list of all available migrations and their status. Start by getting the status of migrations in your application by running the ``status`` command: .. code-block:: terminal $ php bin/console doctrine:migrations:status This command will show you generic information about the migration status, such as how many migrations have been already executed, which still need to run, and the database in use. Now, you can start working with migrations by generating a new blank migration class. Later, you'll learn how Doctrine can generate migrations automatically for you. .. code-block:: terminal $ php bin/console doctrine:migrations:generate Have a look at the newly generated migration class and you will see something like the following: .. code-block:: php declare(strict_types=1); namespace DoctrineMigrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20180605025653 extends AbstractMigration { public function getDescription() : string { return ''; } public function up(Schema $schema) : void { // this up() migration is auto-generated, please modify it to your needs } public function down(Schema $schema) : void { // this down() migration is auto-generated, please modify it to your needs } } If you run the ``status`` command again it will now show that you have one new migration to execute: .. code-block:: terminal $ php bin/console doctrine:migrations:status --show-versions Now you can add some migration code to the ``up()`` and ``down()`` methods and finally migrate when you're ready: .. code-block:: terminal $ php bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20180605025653' For more information on how to write the migrations themselves (i.e. how to fill in the ``up()`` and ``down()`` methods), see the official Doctrine Migrations `documentation`_. Running Migrations during Deployment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Of course, the end goal of writing migrations is to be able to use them to reliably update your database structure when you deploy your application. By running the migrations locally (or on a beta server), you can ensure that the migrations work as you expect. When you do finally deploy your application, you just need to remember to run the ``doctrine:migrations:migrate`` command. Internally, Doctrine creates a ``migration_versions`` table inside your database and tracks which migrations have been executed there. So, no matter how many migrations you've created and executed locally, when you run the command during deployment, Doctrine will know exactly which migrations it hasn't run yet by looking at the ``migration_versions`` table of your production database. Regardless of what server you're on, you can always safely run this command to execute only the migrations that haven't been run yet on *that* particular database. Skipping Migrations ~~~~~~~~~~~~~~~~~~~ You can skip single migrations by explicitly adding them to the ``migration_versions`` table: .. code-block:: terminal $ php bin/console doctrine:migrations:version 'App\Migrations\Version123' --add .. tip:: Pay attention to the single quotes (``'``) used in the command above, without them or with the double quotes (``"``) the command will not work properly. Doctrine will then assume that this migration has already been run and will ignore it. Migration Dependencies ---------------------- Migrations can have dependencies on external services (such as geolocation, mailer, data processing services...) that can be used to have more powerful migrations. Those dependencies are not automatically injected into your migrations but need to be injected using custom migrations factories. Here is an example on how to inject the service container into your migrations: .. configuration-block:: .. code-block:: yaml # config/packages/doctrine_migrations.yaml doctrine_migrations: services: 'Doctrine\Migrations\Version\MigrationFactory': 'App\Migrations\Factory\MigrationFactoryDecorator' # config/services.yaml services: Doctrine\Migrations\Version\DbalMigrationFactory: ~ App\Migrations\Factory\MigrationFactoryDecorator: decorates: Doctrine\Migrations\Version\DbalMigrationFactory arguments: ['@App\Migrations\Factory\MigrationFactoryDecorator.inner', '@service_container'] .. code-block:: php declare(strict_types=1); namespace App\Migrations\Factory; use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\Version\MigrationFactory; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; class MigrationFactoryDecorator implements MigrationFactory { private $migrationFactory; private $container; public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container) { $this->migrationFactory = $migrationFactory; $this->container = $container; } public function createVersion(string $migrationClassName): AbstractMigration { $instance = $this->migrationFactory->createVersion($migrationClassName); if ($instance instanceof ContainerAwareInterface) { $instance->setContainer($this->container); } return $instance; } } .. tip:: If your migration class implements the interface ``Symfony\Component\DependencyInjection\ContainerAwareInterface`` this bundle will automatically inject the default symfony container into your migration class (this because the ``MigrationFactoryDecorator`` shown in this example is the default migration factory used by this bundle). Generating Migrations Automatically ----------------------------------- In reality, you should rarely need to write migrations manually, as the migrations library can generate migration classes automatically by comparing your Doctrine mapping information (i.e. what your database *should* look like) with your actual current database structure. For example, suppose you create a new ``User`` entity and add mapping information for Doctrine's ORM: .. configuration-block:: .. code-block:: php-annotations // src/Entity/User.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="hello_user") */ class User { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $name; .. code-block:: yaml # config/doctrine/User.orm.yaml App\Entity\User: type: entity table: user id: id: type: integer generator: strategy: AUTO fields: name: type: string length: 255 .. code-block:: xml <!-- config/doctrine/User.orm.xml --> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="App\Entity\User" table="user"> <id name="id" type="integer" column="id"> <generator strategy="AUTO"/> </id> <field name="name" column="name" type="string" length="255" /> </entity> </doctrine-mapping> With this information, Doctrine is now ready to help you persist your new ``User`` object to and from the ``user`` table. Of course, this table doesn't exist yet! Generate a new migration for this table automatically by running the following command: .. code-block:: terminal $ php bin/console doctrine:migrations:diff You should see a message that a new migration class was generated based on the schema differences. If you open this file, you'll find that it has the SQL code needed to create the ``user`` table. Next, run the migration to add the table to your database: .. code-block:: terminal $ php bin/console doctrine:migrations:migrate The moral of the story is this: after each change you make to your Doctrine mapping information, run the ``doctrine:migrations:diff`` command to automatically generate your migration classes. If you do this from the very beginning of your project (i.e. so that even the first tables were loaded via a migration class), you'll always be able to create a fresh database and run your migrations in order to get your database schema fully up to date. In fact, this is an easy and dependable workflow for your project. If you don't want to use this workflow and instead create your schema via ``doctrine:schema:create``, you can tell Doctrine to skip all existing migrations: .. code-block:: terminal $ php bin/console doctrine:migrations:version --add --all Otherwise Doctrine will try to run all migrations, which probably will not work. Manual Tables ------------- It is a common use case, that in addition to your generated database structure based on your doctrine entities you might need custom tables. By default such tables will be removed by the ``doctrine:migrations:diff`` command. If you follow a specific scheme you can configure doctrine/dbal to ignore those tables. Let's say all custom tables will be prefixed by ``t_``. In this case you just have to add the following configuration option to your doctrine configuration: .. configuration-block:: .. code-block:: yaml doctrine: dbal: schema_filter: ~^(?!t_)~ .. code-block:: xml <doctrine:dbal schema-filter="~^(?!t_)~" /> .. code-block:: php $container->loadFromExtension('doctrine', array( 'dbal' => array( 'schema_filter' => '~^(?!t_)~', // ... ), // ... )); This ignores the tables, and any named objects such as sequences, on the DBAL level and they will be ignored by the diff command. Note that if you have multiple connections configured then the ``schema_filter`` configuration will need to be placed per-connection. .. _documentation: https://www.doctrine-project.org/projects/doctrine-migrations/en/current/index.html .. _DoctrineMigrationsBundle: https://github.com/doctrine/DoctrineMigrationsBundle .. _`Doctrine Database Migrations`: https://github.com/doctrine/migrations .. _`Symfony Flex`: https://symfony.com/doc/current/setup/flex.html doctrine-migrations-bundle/Resources/views/Collector/icon.svg 0000644 00000000755 15120025737 0020513 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <polygon fill="#AAA" points="0 0 24 0 24 7 17.5 7 16.5 3 15.5 7 11 7 12 3 3 3 4 7 0 7" /> <polygon fill="#AAA" points="0 8.5 4.5 8.5 6 15.5 0 15.5" /> <polygon fill="#AAA" points="10.5 8.5 15 8.5 13.5 15.5 9 15.5" /> <polygon fill="#AAA" points="18 8.5 24 8.5 24 15.5 19.5 15.5" /> <polygon fill="#AAA" points="0 17 6.5 17 7.5 21 8.5 17 13 17 12 21 21 21 20 17 24 17 24 24 0 24" /> </svg> doctrine-migrations-bundle/Resources/views/Collector/migrations.html.twig 0000644 00000015632 15120025737 0023055 0 ustar 00 {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% import _self as helper %} {% block toolbar %} {% set unavailable_migrations = collector.data.unavailable_migrations|length %} {% set new_migrations = collector.data.new_migrations|length %} {% if unavailable_migrations > 0 or new_migrations > 0 %} {% set executed_migrations = collector.data.executed_migrations|length %} {% set available_migrations = collector.data.available_migrations|length %} {% set status_color = unavailable_migrations > 0 ? 'yellow' : '' %} {% set status_color = new_migrations > 0 ? 'red' : status_color %} {% set icon %} {{ include('@DoctrineMigrations/Collector/icon.svg') }} <span class="sf-toolbar-value">{{ new_migrations + unavailable_migrations }}</span> {% endset %} {% set text %} <div class="sf-toolbar-info-piece"> <b>Current</b> <span>{{ executed_migrations > 0 ? collector.data.executed_migrations|last.version|split('\\')|last : 'n/a' }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Executed</b> <span class="sf-toolbar-status">{{ executed_migrations }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Executed Unavailable</b> <span class="sf-toolbar-status {{ unavailable_migrations > 0 ? 'sf-toolbar-status-yellow' }}">{{ unavailable_migrations }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Available</b> <span class="sf-toolbar-status">{{ available_migrations }}</span> </div> <div class="sf-toolbar-info-piece"> <b>New</b> <span class="sf-toolbar-status {{ new_migrations > 0 ? 'sf-toolbar-status-red' }}">{{ new_migrations }}</span> </div> {% endset %} {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }} {% endif %} {% endblock %} {% block menu %} {% set unavailable_migrations = collector.data.unavailable_migrations|length %} {% set new_migrations = collector.data.new_migrations|length %} {% set label = unavailable_migrations > 0 ? 'label-status-warning' : '' %} {% set label = new_migrations > 0 ? 'label-status-error' : label %} <span class="label {{ label }}"> <span class="icon">{{ include('@DoctrineMigrations/Collector/icon.svg') }}</span> <strong>Migrations</strong> {% if unavailable_migrations > 0 or new_migrations > 0 %} <span class="count"> <span>{{ new_migrations + unavailable_migrations }}</span> </span> {% endif %} </span> {% endblock %} {% block panel %} <h2>Doctrine Migrations</h2> <div class="metrics"> <div class="metric"> <span class="value">{{ collector.data.executed_migrations|length }}</span> <span class="label">Executed</span> </div> <div class="metric"> <span class="value">{{ collector.data.unavailable_migrations|length }}</span> <span class="label">Executed Unavailable</span> </div> <div class="metric"> <span class="value">{{ collector.data.available_migrations|length }}</span> <span class="label">Available</span> </div> <div class="metric"> <span class="value">{{ collector.data.new_migrations|length }}</span> <span class="label">New</span> </div> </div> <h3>Configuration</h3> <table> <thead> <tr> <th colspan="2" class="colored font-normal">Storage</th> </tr> </thead> <tr> <td class="font-normal">Type</td> <td class="font-normal">{{ collector.data.storage }}</td> </tr> {% if collector.data.table is defined %} <tr> <td class="font-normal">Table Name</td> <td class="font-normal">{{ collector.data.table }}</td> </tr> {% endif %} {% if collector.data.column is defined %} <tr> <td class="font-normal">Column Name</td> <td class="font-normal">{{ collector.data.column }}</td> </tr> {% endif %} </table> <table> <thead> <tr> <th colspan="2" class="colored font-normal">Database</th> </tr> </thead> <tr> <td class="font-normal">Driver</td> <td class="font-normal">{{ collector.data.driver }}</td> </tr> <tr> <td class="font-normal">Name</td> <td class="font-normal">{{ collector.data.name }}</td> </tr> </table> <table> <thead> <tr> <th colspan="2" class="colored font-normal">Migration Namespaces</th> </tr> </thead> {% for namespace, directory in collector.data.namespaces %} <tr> <td class="font-normal">{{ namespace }}</td> <td class="font-normal">{{ directory }}</td> </tr> {% endfor %} </table> <h3>Migrations</h3> <table> <thead> <tr> <th class="colored font-normal">Version</th> <th class="colored font-normal">Description</th> <th class="colored font-normal">Status</th> <th class="colored font-normal">Executed at</th> <th class="colored font-normal">Execution time</th> </tr> </thead> {% for migration in collector.data.new_migrations %} {{ helper.render_migration(migration) }} {% endfor %} {% for migration in collector.data.executed_migrations|reverse %} {{ helper.render_migration(migration) }} {% endfor %} </table> {% endblock %} {% macro render_migration(migration) %} <tr> <td class="font-normal"> {% if migration.file %} <a href="{{ migration.file|file_link(1) }}" title="{{ migration.file }}">{{ migration.version }}</a> {% else %} {{ migration.version }} {% endif %} </td> <td class="font-normal">{{ migration.description }}</td> <td class="font-normal"> {% if migration.is_new %} <span class="label status-error">NOT EXECUTED</span> {% elseif migration.is_unavailable %} <span class="label status-warning">UNAVAILABLE</span> {% else %} <span class="label status-success">EXECUTED</span> {% endif %} </td> <td class="font-normal">{{ migration.executed_at ? migration.executed_at|date : 'n/a' }}</td> <td class="font-normal">{{ migration.execution_time|default('n/a') }}</td> </tr> {% endmacro %} doctrine-migrations-bundle/Changelog.md 0000644 00000000327 15120025737 0014211 0 ustar 00 ## 1.1.0 (2015-09-29) Features: * Allowed DoctrineMigrationsBundle to work with Symfony apps using DBAL only ## 1.0.1 (2015-05-06) * Allowed Symfony 3.0 components ## 1.0.0 (2014-08-17) Initial stable release doctrine-migrations-bundle/DoctrineMigrationsBundle.php 0000644 00000001071 15120025737 0017444 0 ustar 00 <?php namespace Doctrine\Bundle\MigrationsBundle; use Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass\ConfigureDependencyFactoryPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; /** * Bundle. * * @author Fabien Potencier <fabien@symfony.com> * @author Jonathan H. Wage <jonwage@gmail.com> */ class DoctrineMigrationsBundle extends Bundle { public function build(ContainerBuilder $builder) { $builder->addCompilerPass(new ConfigureDependencyFactoryPass()); } } doctrine-migrations-bundle/LICENSE 0000644 00000002051 15120025737 0013001 0 ustar 00 Copyright (c) 2006-2013 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. doctrine-migrations-bundle/README.markdown 0000644 00000000717 15120025737 0014504 0 ustar 00 DoctrineMigrationsBundle ======================== This bundle integrates the [Doctrine2 Migrations library](http://www.doctrine-project.org/projects/migrations.html) into Symfony applications. Database migrations help you version the changes in your database schema and apply them in a predictable way on every server running the application. [Read the documentation of this bundle](https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html). doctrine-migrations-bundle/UPGRADE.md 0000644 00000004260 15120025737 0013411 0 ustar 00 # Upgrade ## From 2.x to 3.0.0 - The configuration for the migration namespace and directory changed as follows: Before ```yaml doctrine_migrations: dir_name: '%kernel.project_dir%/src/Migrations' namespace: DoctrineMigrations ``` After ```yaml doctrine_migrations: migrations_paths: 'DoctrineMigrations': '%kernel.project_dir%/src/Migrations' ``` - The configuration for the metadata table definition changed as follows: Before ```yaml doctrine_migrations: table_name: 'migration_versions' column_name: 'version' column_length: 14 executed_at_column_name: 'executed_at' ``` After ```yaml doctrine_migrations: storage: table_storage: table_name: 'migration_versions' version_column_name: 'version' version_column_length: 191 executed_at_column_name: 'executed_at' ``` If your project did not originally specify its own table definition configuration, you will need to configure the table name after the upgrade: ```yaml doctrine_migrations: storage: table_storage: table_name: 'migration_versions' ``` and then run the `doctrine:migrations:sync-metadata-storage` command. - The migration name has been dropped: Before ```yaml doctrine_migrations: name: 'Application Migrations' ``` After The parameter `name` has been dropped. - The default for `table_name` changed from `migration_versions` to `doctrine_migration_versions`. If you did not specify the `table_name` option, you now need to declare it explicitly to not lose migration data. ```yaml doctrine_migrations: storage: table_storage: table_name: 'migration_versions' ``` ### Underlying doctrine/migrations library Upgrading this bundle to `3.0` will also update the `doctrine/migrations` library to the version `3.0`. Backward incompatible changes in `doctrine/migrations` 3.0 are documented in the dedicated [UPGRADE](https://github.com/doctrine/migrations/blob/3.0.x/UPGRADE.md) document. - The container is not automatically injected anymore when a migration implements `ContainerAwareInterface`. Custom migration factories should be used to inject additional dependencies into migrations. doctrine-migrations-bundle/composer.json 0000644 00000002664 15120025737 0014530 0 ustar 00 { "name": "doctrine/doctrine-migrations-bundle", "type": "symfony-bundle", "description": "Symfony DoctrineMigrationsBundle", "keywords": ["DBAL", "Migrations", "Schema"], "homepage": "https://www.doctrine-project.org", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org" }, { "name": "Symfony Community", "homepage": "http://symfony.com/contributors" } ], "require": { "php": "^7.2|^8.0", "symfony/framework-bundle": "~3.4|~4.0|~5.0", "doctrine/doctrine-bundle": "~1.0|~2.0", "doctrine/migrations": "^3.1" }, "require-dev": { "phpunit/phpunit": "^7.0|^8.0|^9.0", "doctrine/coding-standard": "^8.0", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", "doctrine/orm": "^2.6", "doctrine/persistence": "^1.3||^2.0" }, "autoload": { "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "autoload-dev": { "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\Tests\\": "Tests" } } } doctrine-migrations-bundle/phpstan.neon.dist 0000644 00000001037 15120025737 0015277 0 ustar 00 parameters: level: 7 paths: - %currentWorkingDirectory%/DependencyInjection - %currentWorkingDirectory%/Tests ignoreErrors: - '~.*TreeBuilder.*getRootNode.*~' - '~.*TreeBuilder::root.*~' - '~Parameter \#1 \$configs.*DoctrineMigrationsExtension::load.*~' excludes_analyse: - Tests/Fixtures/CustomEntityManager.php includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon annotations/docs/en/annotations.rst 0000644 00000023130 15120025737 0013523 0 ustar 00 Handling Annotations ==================== There are several different approaches to handling annotations in PHP. Doctrine Annotations maps docblock annotations to PHP classes. Because not all docblock annotations are used for metadata purposes a filter is applied to ignore or skip classes that are not Doctrine annotations. Take a look at the following code snippet: .. code-block:: php namespace MyProject\Entities; use Doctrine\ORM\Mapping AS ORM; use Symfony\Component\Validator\Constraints AS Assert; /** * @author Benjamin Eberlei * @ORM\Entity * @MyProject\Annotations\Foobarable */ class User { /** * @ORM\Id @ORM\Column @ORM\GeneratedValue * @dummy * @var int */ private $id; /** * @ORM\Column(type="string") * @Assert\NotEmpty * @Assert\Email * @var string */ private $email; } In this snippet you can see a variety of different docblock annotations: - Documentation annotations such as ``@var`` and ``@author``. These annotations are ignored and never considered for throwing an exception due to wrongly used annotations. - Annotations imported through use statements. The statement ``use Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace available as ``@ORM\ClassName``. Same goes for the import of ``@Assert``. - The ``@dummy`` annotation. It is not a documentation annotation and not ignored. For Doctrine Annotations it is not entirely clear how to handle this annotation. Depending on the configuration an exception (unknown annotation) will be thrown when parsing this annotation. - The fully qualified annotation ``@MyProject\Annotations\Foobarable``. This is transformed directly into the given class name. How are these annotations loaded? From looking at the code you could guess that the ORM Mapping, Assert Validation and the fully qualified annotation can just be loaded using the defined PHP autoloaders. This is not the case however: For error handling reasons every check for class existence inside the ``AnnotationReader`` sets the second parameter $autoload of ``class_exists($name, $autoload)`` to false. To work flawlessly the ``AnnotationReader`` requires silent autoloaders which many autoloaders are not. Silent autoloading is NOT part of the `PSR-0 specification <https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_ for autoloading. This is why Doctrine Annotations uses its own autoloading mechanism through a global registry. If you are wondering about the annotation registry being global, there is no other way to solve the architectural problems of autoloading annotation classes in a straightforward fashion. Additionally if you think about PHP autoloading then you recognize it is a global as well. To anticipate the configuration section, making the above PHP class work with Doctrine Annotations requires this setup: .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); $reader = new AnnotationReader(); AnnotationReader::addGlobalIgnoredName('dummy'); The second block with the annotation registry calls registers all the three different annotation namespaces that are used. Doctrine Annotations saves all its annotations in a single file, that is why ``AnnotationRegistry#registerFile`` is used in contrast to ``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 compatible loading mechanism for class to file names. In the third block, we create the actual ``AnnotationReader`` instance. Note that we also add ``dummy`` to the global list of ignored annotations for which we do not throw exceptions. Setting this is necessary in our example case, otherwise ``@dummy`` would trigger an exception to be thrown during the parsing of the docblock of ``MyProject\Entities\User#id``. Setup and Configuration ----------------------- To use the annotations library is simple, you just need to create a new ``AnnotationReader`` instance: .. code-block:: php $reader = new \Doctrine\Common\Annotations\AnnotationReader(); This creates a simple annotation reader with no caching other than in memory (in php arrays). Since parsing docblocks can be expensive you should cache this process by using a caching reader. To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``. This reader decorates the original reader and stores all annotations in a PSR-6 cache: .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\PsrCachedReader; $cache = ... // instantiate a PSR-6 Cache pool $reader = new PsrCachedReader( new AnnotationReader(), $cache, $debug = true ); The ``debug`` flag is used here as well to invalidate the cache files when the PHP class with annotations changed and should be used during development. .. warning :: The ``AnnotationReader`` works and caches under the assumption that all annotations of a doc-block are processed at once. That means that annotation classes that do not exist and aren't loaded and cannot be autoloaded (using the AnnotationRegistry) would never be visible and not accessible if a cache is used unless the cache is cleared and the annotations requested again, this time with all annotations defined. By default the annotation reader returns a list of annotations with numeric indexes. If you want your annotations to be indexed by their class name you can wrap the reader in an ``IndexedReader``: .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\IndexedReader; $reader = new IndexedReader(new AnnotationReader()); .. warning:: You should never wrap the indexed reader inside a cached reader, only the other way around. This way you can re-use the cache with indexed or numeric keys, otherwise your code may experience failures due to caching in a numerical or indexed format. Registering Annotations ~~~~~~~~~~~~~~~~~~~~~~~ As explained in the introduction, Doctrine Annotations uses its own autoloading mechanism to determine if a given annotation has a corresponding PHP class that can be autoloaded. For annotation autoloading you have to configure the ``Doctrine\Common\Annotations\AnnotationRegistry``. There are three different mechanisms to configure annotation autoloading: - Calling ``AnnotationRegistry#registerFile($file)`` to register a file that contains one or more annotation classes. - Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = null)`` to register that the given namespace contains annotations and that their base directory is located at the given $dirs or in the include path if ``NULL`` is passed. The given directories should *NOT* be the directory where classes of the namespace are in, but the base directory of the root namespace. The AnnotationRegistry uses a namespace to directory separator approach to resolve the correct path. - Calling ``AnnotationRegistry#registerLoader($callable)`` to register an autoloader callback. The callback accepts the class as first and only parameter and has to return ``true`` if the corresponding file was found and included. .. note:: Loaders have to fail silently, if a class is not found even if it matches for example the namespace prefix of that loader. Never is a loader to throw a warning or exception if the loading failed otherwise parsing doc block annotations will become a huge pain. A sample loader callback could look like: .. code-block:: php use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Component\ClassLoader\UniversalClassLoader; AnnotationRegistry::registerLoader(function($class) { $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; if (file_exists("/my/base/path/" . $file)) { // file_exists() makes sure that the loader fails silently require "/my/base/path/" . $file; } }); $loader = new UniversalClassLoader(); AnnotationRegistry::registerLoader(array($loader, "loadClass")); Ignoring missing exceptions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default an exception is thrown from the ``AnnotationReader`` if an annotation was found that: - is not part of the list of ignored "documentation annotations"; - was not imported through a use statement; - is not a fully qualified class that exists. You can disable this behavior for specific names if your docblocks do not follow strict requirements: .. code-block:: php $reader = new \Doctrine\Common\Annotations\AnnotationReader(); AnnotationReader::addGlobalIgnoredName('foo'); PHP Imports ~~~~~~~~~~~ By default the annotation reader parses the use-statement of a php file to gain access to the import rules and register them for the annotation processing. Only if you are using PHP Imports can you validate the correct usage of annotations and throw exceptions if you misspelled an annotation. This mechanism is enabled by default. To ease the upgrade path, we still allow you to disable this mechanism. Note however that we will remove this in future versions: .. code-block:: php $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $reader->setEnabledPhpImports(false); annotations/docs/en/custom.rst 0000644 00000023616 15120025737 0012511 0 ustar 00 Custom Annotation Classes ========================= If you want to define your own annotations, you just have to group them in a namespace and register this namespace in the ``AnnotationRegistry``. Annotation classes have to contain a class-level docblock with the text ``@Annotation``: .. code-block:: php namespace MyCompany\Annotations; /** @Annotation */ class Bar { // some code } Inject annotation values ------------------------ The annotation parser checks if the annotation constructor has arguments, if so then it will pass the value array, otherwise it will try to inject values into public properties directly: .. code-block:: php namespace MyCompany\Annotations; /** * @Annotation * * Some Annotation using a constructor */ class Bar { private $foo; public function __construct(array $values) { $this->foo = $values['foo']; } } /** * @Annotation * * Some Annotation without a constructor */ class Foo { public $bar; } Optional: Constructors with Named Parameters -------------------------------------------- Starting with Annotations v1.11 a new annotation instantiation strategy is available that aims at compatibility of Annotation classes with the PHP 8 attribute feature. You need to declare a constructor with regular parameter names that match the named arguments in the annotation syntax. To enable this feature, you can tag your annotation class with ``@NamedArgumentConstructor`` (available from v1.12) or implement the ``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface (available from v1.11 and deprecated as of v1.12). When using the ``@NamedArgumentConstructor`` tag, the first argument of the constructor is considered as the default one. Usage with the ``@NamedArgumentConstructor`` tag .. code-block:: php namespace MyCompany\Annotations; /** * @Annotation * @NamedArgumentConstructor */ class Bar implements NamedArgumentConstructorAnnotation { private $foo; public function __construct(string $foo) { $this->foo = $foo; } } /** Usable with @Bar(foo="baz") */ /** Usable with @Bar("baz") */ In combination with PHP 8's constructor property promotion feature you can simplify this to: .. code-block:: php namespace MyCompany\Annotations; /** * @Annotation * @NamedArgumentConstructor */ class Bar implements NamedArgumentConstructorAnnotation { public function __construct(private string $foo) {} } Usage with the ``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface (v1.11, deprecated as of v1.12): .. code-block:: php namespace MyCompany\Annotations; use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation; /** @Annotation */ class Bar implements NamedArgumentConstructorAnnotation { private $foo; public function __construct(private string $foo) {} } /** Usable with @Bar(foo="baz") */ Annotation Target ----------------- ``@Target`` indicates the kinds of class elements to which an annotation type is applicable. Then you could define one or more targets: - ``CLASS`` Allowed in class docblocks - ``PROPERTY`` Allowed in property docblocks - ``METHOD`` Allowed in the method docblocks - ``FUNCTION`` Allowed in function dockblocks - ``ALL`` Allowed in class, property, method and function docblocks - ``ANNOTATION`` Allowed inside other annotations If the annotations is not allowed in the current context, an ``AnnotationException`` is thrown. .. code-block:: php namespace MyCompany\Annotations; /** * @Annotation * @Target({"METHOD","PROPERTY"}) */ class Bar { // some code } /** * @Annotation * @Target("CLASS") */ class Foo { // some code } Attribute types --------------- The annotation parser checks the given parameters using the phpdoc annotation ``@var``, The data type could be validated using the ``@var`` annotation on the annotation properties or using the ``@Attributes`` and ``@Attribute`` annotations. If the data type does not match you get an ``AnnotationException`` .. code-block:: php namespace MyCompany\Annotations; /** * @Annotation * @Target({"METHOD","PROPERTY"}) */ class Bar { /** @var mixed */ public $mixed; /** @var boolean */ public $boolean; /** @var bool */ public $bool; /** @var float */ public $float; /** @var string */ public $string; /** @var integer */ public $integer; /** @var array */ public $array; /** @var SomeAnnotationClass */ public $annotation; /** @var array<integer> */ public $arrayOfIntegers; /** @var array<SomeAnnotationClass> */ public $arrayOfAnnotations; } /** * @Annotation * @Target({"METHOD","PROPERTY"}) * @Attributes({ * @Attribute("stringProperty", type = "string"), * @Attribute("annotProperty", type = "SomeAnnotationClass"), * }) */ class Foo { public function __construct(array $values) { $this->stringProperty = $values['stringProperty']; $this->annotProperty = $values['annotProperty']; } // some code } Annotation Required ------------------- ``@Required`` indicates that the field must be specified when the annotation is used. If it is not used you get an ``AnnotationException`` stating that this value can not be null. Declaring a required field: .. code-block:: php /** * @Annotation * @Target("ALL") */ class Foo { /** @Required */ public $requiredField; } Usage: .. code-block:: php /** @Foo(requiredField="value") */ public $direction; // Valid /** @Foo */ public $direction; // Required field missing, throws an AnnotationException Enumerated values ----------------- - An annotation property marked with ``@Enum`` is a field that accepts a fixed set of scalar values. - You should use ``@Enum`` fields any time you need to represent fixed values. - The annotation parser checks the given value and throws an ``AnnotationException`` if the value does not match. Declaring an enumerated property: .. code-block:: php /** * @Annotation * @Target("ALL") */ class Direction { /** * @Enum({"NORTH", "SOUTH", "EAST", "WEST"}) */ public $value; } Annotation usage: .. code-block:: php /** @Direction("NORTH") */ public $direction; // Valid value /** @Direction("NORTHEAST") */ public $direction; // Invalid value, throws an AnnotationException Constants --------- The use of constants and class constants is available on the annotations parser. The following usages are allowed: .. code-block:: php namespace MyCompany\Entity; use MyCompany\Annotations\Foo; use MyCompany\Annotations\Bar; use MyCompany\Entity\SomeClass; /** * @Foo(PHP_EOL) * @Bar(Bar::FOO) * @Foo({SomeClass::FOO, SomeClass::BAR}) * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE}) */ class User { } Be careful with constants and the cache ! .. note:: The cached reader will not re-evaluate each time an annotation is loaded from cache. When a constant is changed the cache must be cleaned. Usage ----- Using the library API is simple. Using the annotations described in the previous section, you can now annotate other classes with your annotations: .. code-block:: php namespace MyCompany\Entity; use MyCompany\Annotations\Foo; use MyCompany\Annotations\Bar; /** * @Foo(bar="foo") * @Bar(foo="bar") */ class User { } Now we can write a script to get the annotations above: .. code-block:: php $reflClass = new ReflectionClass('MyCompany\Entity\User'); $classAnnotations = $reader->getClassAnnotations($reflClass); foreach ($classAnnotations AS $annot) { if ($annot instanceof \MyCompany\Annotations\Foo) { echo $annot->bar; // prints "foo"; } else if ($annot instanceof \MyCompany\Annotations\Bar) { echo $annot->foo; // prints "bar"; } } You have a complete API for retrieving annotation class instances from a class, property or method docblock: Reader API ~~~~~~~~~~ Access all annotations of a class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getClassAnnotations(\ReflectionClass $class); Access one annotation of a class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getClassAnnotation(\ReflectionClass $class, $annotationName); Access all annotations of a method ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getMethodAnnotations(\ReflectionMethod $method); Access one annotation of a method ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getMethodAnnotation(\ReflectionMethod $method, $annotationName); Access all annotations of a property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getPropertyAnnotations(\ReflectionProperty $property); Access one annotation of a property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); Access all annotations of a function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getFunctionAnnotations(\ReflectionFunction $property); Access one annotation of a function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName); annotations/docs/en/index.rst 0000644 00000006313 15120025737 0012301 0 ustar 00 Deprecation notice ================== PHP 8 introduced `attributes <https://www.php.net/manual/en/language.attributes.overview.php>`_, which are a native replacement for annotations. As such, this library is considered feature complete, and should receive exclusively bugfixes and security fixes. Introduction ============ Doctrine Annotations allows to implement custom annotation functionality for PHP classes and functions. .. code-block:: php class Foo { /** * @MyAnnotation(myProperty="value") */ private $bar; } Annotations aren't implemented in PHP itself which is why this component offers a way to use the PHP doc-blocks as a place for the well known annotation syntax using the ``@`` char. Annotations in Doctrine are used for the ORM configuration to build the class mapping, but it can be used in other projects for other purposes too. Installation ============ You can install the Annotation component with composer: .. code-block:: $ composer require doctrine/annotations Create an annotation class ========================== An annotation class is a representation of the later used annotation configuration in classes. The annotation class of the previous example looks like this: .. code-block:: php /** * @Annotation */ final class MyAnnotation { public $myProperty; } The annotation class is declared as an annotation by ``@Annotation``. :ref:`Read more about custom annotations. <custom>` Reading annotations =================== The access to the annotations happens by reflection of the class or function containing them. There are multiple reader-classes implementing the ``Doctrine\Common\Annotations\Reader`` interface, that can access the annotations of a class. A common one is ``Doctrine\Common\Annotations\AnnotationReader``: .. code-block:: php use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; // Deprecated and will be removed in 2.0 but currently needed AnnotationRegistry::registerLoader('class_exists'); $reflectionClass = new ReflectionClass(Foo::class); $property = $reflectionClass->getProperty('bar'); $reader = new AnnotationReader(); $myAnnotation = $reader->getPropertyAnnotation( $property, MyAnnotation::class ); echo $myAnnotation->myProperty; // result: "value" Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works if you already have an autoloader configured (i.e. composer autoloader). Otherwise, :ref:`please take a look to the other annotation autoload mechanisms <annotations>`. A reader has multiple methods to access the annotations of a class or function. :ref:`Read more about handling annotations. <annotations>` IDE Support ----------- Some IDEs already provide support for annotations: - Eclipse via the `Symfony2 Plugin <https://github.com/pulse00/Symfony-2-Eclipse-Plugin>`_ - PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_ .. _Read more about handling annotations.: annotations .. _Read more about custom annotations.: custom annotations/docs/en/sidebar.rst 0000644 00000000101 15120025737 0012570 0 ustar 00 .. toctree:: :depth: 3 index annotations custom annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php 0000644 00000000547 15120025737 0021741 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; /** * Annotation that can be used to signal to the parser * to check the attribute type during the parsing process. * * @Annotation */ final class Attribute { /** @var string */ public $name; /** @var string */ public $type; /** @var bool */ public $required = false; } annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php 0000644 00000000447 15120025737 0022123 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; /** * Annotation that can be used to signal to the parser * to check the types of all declared attributes during the parsing process. * * @Annotation */ final class Attributes { /** @var array<Attribute> */ public $value; } annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php 0000644 00000003312 15120025737 0020673 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; use InvalidArgumentException; use function get_class; use function gettype; use function in_array; use function is_object; use function is_scalar; use function sprintf; /** * Annotation that can be used to signal to the parser * to check the available values during the parsing process. * * @Annotation * @Attributes({ * @Attribute("value", required = true, type = "array"), * @Attribute("literal", required = false, type = "array") * }) */ final class Enum { /** @phpstan-var list<scalar> */ public $value; /** * Literal target declaration. * * @var mixed[] */ public $literal; /** * @phpstan-param array{literal?: mixed[], value: list<scalar>} $values * * @throws InvalidArgumentException */ public function __construct(array $values) { if (! isset($values['literal'])) { $values['literal'] = []; } foreach ($values['value'] as $var) { if (! is_scalar($var)) { throw new InvalidArgumentException(sprintf( '@Enum supports only scalar values "%s" given.', is_object($var) ? get_class($var) : gettype($var) )); } } foreach ($values['literal'] as $key => $var) { if (! in_array($key, $values['value'])) { throw new InvalidArgumentException(sprintf( 'Undefined enumerator value "%s" for literal "%s".', $key, $var )); } } $this->value = $values['value']; $this->literal = $values['literal']; } } annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php 0000644 00000001752 15120025740 0023245 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; use RuntimeException; use function is_array; use function is_string; use function json_encode; use function sprintf; /** * Annotation that can be used to signal to the parser to ignore specific * annotations during the parsing process. * * @Annotation */ final class IgnoreAnnotation { /** @phpstan-var list<string> */ public $names; /** * @phpstan-param array{value: string|list<string>} $values * * @throws RuntimeException */ public function __construct(array $values) { if (is_string($values['value'])) { $values['value'] = [$values['value']]; } if (! is_array($values['value'])) { throw new RuntimeException(sprintf( '@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value']) )); } $this->names = $values['value']; } } annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php 0000644 00000000371 15120025740 0024760 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; /** * Annotation that indicates that the annotated class should be constructed with a named argument call. * * @Annotation * @Target("CLASS") */ final class NamedArgumentConstructor { } annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php 0000644 00000000352 15120025740 0021542 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; /** * Annotation that can be used to signal to the parser * to check if that attribute is required during the parsing process. * * @Annotation */ final class Required { } annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php 0000644 00000005112 15120025740 0021207 0 ustar 00 <?php namespace Doctrine\Common\Annotations\Annotation; use InvalidArgumentException; use function array_keys; use function get_class; use function gettype; use function implode; use function is_array; use function is_object; use function is_string; use function sprintf; /** * Annotation that can be used to signal to the parser * to check the annotation target during the parsing process. * * @Annotation */ final class Target { public const TARGET_CLASS = 1; public const TARGET_METHOD = 2; public const TARGET_PROPERTY = 4; public const TARGET_ANNOTATION = 8; public const TARGET_FUNCTION = 16; public const TARGET_ALL = 31; /** @var array<string, int> */ private static $map = [ 'ALL' => self::TARGET_ALL, 'CLASS' => self::TARGET_CLASS, 'METHOD' => self::TARGET_METHOD, 'PROPERTY' => self::TARGET_PROPERTY, 'FUNCTION' => self::TARGET_FUNCTION, 'ANNOTATION' => self::TARGET_ANNOTATION, ]; /** @phpstan-var list<string> */ public $value; /** * Targets as bitmask. * * @var int */ public $targets; /** * Literal target declaration. * * @var string */ public $literal; /** * @phpstan-param array{value?: string|list<string>} $values * * @throws InvalidArgumentException */ public function __construct(array $values) { if (! isset($values['value'])) { $values['value'] = null; } if (is_string($values['value'])) { $values['value'] = [$values['value']]; } if (! is_array($values['value'])) { throw new InvalidArgumentException( sprintf( '@Target expects either a string value, or an array of strings, "%s" given.', is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) ) ); } $bitmask = 0; foreach ($values['value'] as $literal) { if (! isset(self::$map[$literal])) { throw new InvalidArgumentException( sprintf( 'Invalid Target "%s". Available targets: [%s]', $literal, implode(', ', array_keys(self::$map)) ) ); } $bitmask |= self::$map[$literal]; } $this->targets = $bitmask; $this->value = $values['value']; $this->literal = implode(', ', $this->value); } } annotations/lib/Doctrine/Common/Annotations/Annotation.php 0000644 00000002452 15120025740 0017765 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use BadMethodCallException; use function sprintf; /** * Annotations class. */ class Annotation { /** * Value property. Common among all derived classes. * * @var mixed */ public $value; /** @param array<string, mixed> $data Key-value for properties to be defined in this class. */ final public function __construct(array $data) { foreach ($data as $key => $value) { $this->$key = $value; } } /** * Error handler for unknown property accessor in Annotation class. * * @param string $name Unknown property name. * * @throws BadMethodCallException */ public function __get($name) { throw new BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) ); } /** * Error handler for unknown property mutator in Annotation class. * * @param string $name Unknown property name. * @param mixed $value Property value. * * @throws BadMethodCallException */ public function __set($name, $value) { throw new BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class) ); } } annotations/lib/Doctrine/Common/Annotations/AnnotationException.php 0000644 00000011063 15120025740 0021642 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Exception; use Throwable; use function get_class; use function gettype; use function implode; use function is_object; use function sprintf; /** * Description of AnnotationException */ class AnnotationException extends Exception { /** * Creates a new AnnotationException describing a Syntax error. * * @param string $message Exception message * * @return AnnotationException */ public static function syntaxError($message) { return new self('[Syntax Error] ' . $message); } /** * Creates a new AnnotationException describing a Semantical error. * * @param string $message Exception message * * @return AnnotationException */ public static function semanticalError($message) { return new self('[Semantical Error] ' . $message); } /** * Creates a new AnnotationException describing an error which occurred during * the creation of the annotation. * * @param string $message * * @return AnnotationException */ public static function creationError($message, ?Throwable $previous = null) { return new self('[Creation Error] ' . $message, 0, $previous); } /** * Creates a new AnnotationException describing a type error. * * @param string $message * * @return AnnotationException */ public static function typeError($message) { return new self('[Type Error] ' . $message); } /** * Creates a new AnnotationException describing a constant semantical error. * * @param string $identifier * @param string $context * * @return AnnotationException */ public static function semanticalErrorConstants($identifier, $context = null) { return self::semanticalError(sprintf( "Couldn't find constant %s%s.", $identifier, $context ? ', ' . $context : '' )); } /** * Creates a new AnnotationException describing an type error of an attribute. * * @param string $attributeName * @param string $annotationName * @param string $context * @param string $expected * @param mixed $actual * * @return AnnotationException */ public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual) { return self::typeError(sprintf( 'Attribute "%s" of @%s declared on %s expects %s, but got %s.', $attributeName, $annotationName, $context, $expected, is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual) )); } /** * Creates a new AnnotationException describing an required error of an attribute. * * @param string $attributeName * @param string $annotationName * @param string $context * @param string $expected * * @return AnnotationException */ public static function requiredError($attributeName, $annotationName, $context, $expected) { return self::typeError(sprintf( 'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.', $attributeName, $annotationName, $context, $expected )); } /** * Creates a new AnnotationException describing a invalid enummerator. * * @param string $attributeName * @param string $annotationName * @param string $context * @param mixed $given * @phpstan-param list<string> $available * * @return AnnotationException */ public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) { return new self(sprintf( '[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.', $attributeName, $annotationName, $context, implode(', ', $available), is_object($given) ? get_class($given) : $given )); } /** @return AnnotationException */ public static function optimizerPlusSaveComments() { return new self( 'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.' ); } /** @return AnnotationException */ public static function optimizerPlusLoadComments() { return new self( 'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.' ); } } annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php 0000644 00000026371 15120025740 0021116 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation; use Doctrine\Common\Annotations\Annotation\Target; use ReflectionClass; use ReflectionFunction; use ReflectionMethod; use ReflectionProperty; use function array_merge; use function class_exists; use function extension_loaded; use function ini_get; /** * A reader for docblock annotations. */ class AnnotationReader implements Reader { /** * Global map for imports. * * @var array<string, class-string> */ private static $globalImports = [ 'ignoreannotation' => Annotation\IgnoreAnnotation::class, ]; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array<string, true> */ private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names are case sensitive. * * @var array<string, true> */ private static $globalIgnoredNamespaces = []; /** * Add a new annotation to the globally ignored annotation names with regard to exception handling. * * @param string $name */ public static function addGlobalIgnoredName($name) { self::$globalIgnoredNames[$name] = true; } /** * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling. * * @param string $namespace */ public static function addGlobalIgnoredNamespace($namespace) { self::$globalIgnoredNamespaces[$namespace] = true; } /** * Annotations parser. * * @var DocParser */ private $parser; /** * Annotations parser used to collect parsing metadata. * * @var DocParser */ private $preParser; /** * PHP parser used to collect imports. * * @var PhpParser */ private $phpParser; /** * In-memory cache mechanism to store imported annotations per class. * * @psalm-var array<'class'|'function', array<string, array<string, class-string>>> */ private $imports = []; /** * In-memory cache mechanism to store ignored annotations per class. * * @psalm-var array<'class'|'function', array<string, array<string, true>>> */ private $ignoredAnnotationNames = []; /** * Initializes a new AnnotationReader. * * @throws AnnotationException */ public function __construct(?DocParser $parser = null) { if ( extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' || ini_get('opcache.save_comments') === '0') ) { throw AnnotationException::optimizerPlusSaveComments(); } if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) { throw AnnotationException::optimizerPlusSaveComments(); } // Make sure that the IgnoreAnnotation annotation is loaded class_exists(IgnoreAnnotation::class); $this->parser = $parser ?: new DocParser(); $this->preParser = new DocParser(); $this->preParser->setImports(self::$globalImports); $this->preParser->setIgnoreNotImportedAnnotations(true); $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames); $this->phpParser = new PhpParser(); } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $this->parser->setTarget(Target::TARGET_CLASS); $this->parser->setImports($this->getImports($class)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $context = 'property ' . $class->getName() . '::$' . $property->getName(); $this->parser->setTarget(Target::TARGET_PROPERTY); $this->parser->setImports($this->getPropertyImports($property)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($property->getDocComment(), $context); } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; $this->parser->setTarget(Target::TARGET_METHOD); $this->parser->setImports($this->getMethodImports($method)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($method->getDocComment(), $context); } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Gets the annotations applied to a function. * * @phpstan-return list<object> An array of Annotations. */ public function getFunctionAnnotations(ReflectionFunction $function): array { $context = 'function ' . $function->getName(); $this->parser->setTarget(Target::TARGET_FUNCTION); $this->parser->setImports($this->getImports($function)); $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function)); $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces); return $this->parser->parse($function->getDocComment(), $context); } /** * Gets a function annotation. * * @return object|null The Annotation or NULL, if the requested annotation does not exist. */ public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName) { $annotations = $this->getFunctionAnnotations($function); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Returns the ignored annotations for the given class or function. * * @param ReflectionClass|ReflectionFunction $reflection * * @return array<string, true> */ private function getIgnoredAnnotationNames($reflection): array { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); if (isset($this->ignoredAnnotationNames[$type][$name])) { return $this->ignoredAnnotationNames[$type][$name]; } $this->collectParsingMetadata($reflection); return $this->ignoredAnnotationNames[$type][$name]; } /** * Retrieves imports for a class or a function. * * @param ReflectionClass|ReflectionFunction $reflection * * @return array<string, class-string> */ private function getImports($reflection): array { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); if (isset($this->imports[$type][$name])) { return $this->imports[$type][$name]; } $this->collectParsingMetadata($reflection); return $this->imports[$type][$name]; } /** * Retrieves imports for methods. * * @return array<string, class-string> */ private function getMethodImports(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $classImports = $this->getImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if ( ! $trait->hasMethod($method->getName()) || $trait->getFileName() !== $method->getFileName() ) { continue; } $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); } return array_merge($classImports, $traitImports); } /** * Retrieves imports for properties. * * @return array<string, class-string> */ private function getPropertyImports(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $classImports = $this->getImports($class); $traitImports = []; foreach ($class->getTraits() as $trait) { if (! $trait->hasProperty($property->getName())) { continue; } $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait)); } return array_merge($classImports, $traitImports); } /** * Collects parsing metadata for a given class or function. * * @param ReflectionClass|ReflectionFunction $reflection */ private function collectParsingMetadata($reflection): void { $type = $reflection instanceof ReflectionClass ? 'class' : 'function'; $name = $reflection->getName(); $ignoredAnnotationNames = self::$globalIgnoredNames; $annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name); foreach ($annotations as $annotation) { if (! ($annotation instanceof IgnoreAnnotation)) { continue; } foreach ($annotation->names as $annot) { $ignoredAnnotationNames[$annot] = true; } } $this->imports[$type][$name] = array_merge( self::$globalImports, $this->phpParser->parseUseStatements($reflection), [ '__NAMESPACE__' => $reflection->getNamespaceName(), 'self' => $name, ] ); $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames; } } annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php 0000644 00000013121 15120025740 0021511 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use function array_key_exists; use function array_merge; use function class_exists; use function in_array; use function is_file; use function str_replace; use function stream_resolve_include_path; use function strpos; use const DIRECTORY_SEPARATOR; final class AnnotationRegistry { /** * A map of namespaces to use for autoloading purposes based on a PSR-0 convention. * * Contains the namespace as key and an array of directories as value. If the value is NULL * the include path is used for checking for the corresponding file. * * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. * * @var string[][]|string[]|null[] */ private static $autoloadNamespaces = []; /** * A map of autoloader callables. * * @var callable[] */ private static $loaders = []; /** * An array of classes which cannot be found * * @var null[] indexed by class name */ private static $failedToAutoload = []; /** * Whenever registerFile() was used. Disables use of standard autoloader. * * @var bool */ private static $registerFileUsed = false; public static function reset(): void { self::$autoloadNamespaces = []; self::$loaders = []; self::$failedToAutoload = []; self::$registerFileUsed = false; } /** * Registers file. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. */ public static function registerFile(string $file): void { self::$registerFileUsed = true; require_once $file; } /** * Adds a namespace with one or many directories to look for files or null for the include path. * * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. * * @phpstan-param string|list<string>|null $dirs */ public static function registerAutoloadNamespace(string $namespace, $dirs = null): void { self::$autoloadNamespaces[$namespace] = $dirs; } /** * Registers multiple namespaces. * * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. * * @param string[][]|string[]|null[] $namespaces indexed by namespace name */ public static function registerAutoloadNamespaces(array $namespaces): void { self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); } /** * Registers an autoloading callable for annotations, much like spl_autoload_register(). * * NOTE: These class loaders HAVE to be silent when a class was not found! * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. */ public static function registerLoader(callable $callable): void { // Reset our static cache now that we have a new loader to work with self::$failedToAutoload = []; self::$loaders[] = $callable; } /** * Registers an autoloading callable for annotations, if it is not already registered * * @deprecated This method is deprecated and will be removed in * doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. */ public static function registerUniqueLoader(callable $callable): void { if (in_array($callable, self::$loaders, true)) { return; } self::registerLoader($callable); } /** * Autoloads an annotation class silently. */ public static function loadAnnotationClass(string $class): bool { if (class_exists($class, false)) { return true; } if (array_key_exists($class, self::$failedToAutoload)) { return false; } foreach (self::$autoloadNamespaces as $namespace => $dirs) { if (strpos($class, $namespace) !== 0) { continue; } $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; if ($dirs === null) { $path = stream_resolve_include_path($file); if ($path) { require $path; return true; } } else { foreach ((array) $dirs as $dir) { if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { require $dir . DIRECTORY_SEPARATOR . $file; return true; } } } } foreach (self::$loaders as $loader) { if ($loader($class) === true) { return true; } } if ( self::$loaders === [] && self::$autoloadNamespaces === [] && self::$registerFileUsed === false && class_exists($class) ) { return true; } self::$failedToAutoload[$class] = null; return false; } } annotations/lib/Doctrine/Common/Annotations/CachedReader.php 0000644 00000016122 15120025740 0020144 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Doctrine\Common\Cache\Cache; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use function array_map; use function array_merge; use function assert; use function filemtime; use function max; use function time; /** * A cache aware annotation reader. * * @deprecated the CachedReader is deprecated and will be removed * in version 2.0.0 of doctrine/annotations. Please use the * {@see \Doctrine\Common\Annotations\PsrCachedReader} instead. */ final class CachedReader implements Reader { /** @var Reader */ private $delegate; /** @var Cache */ private $cache; /** @var bool */ private $debug; /** @var array<string, array<object>> */ private $loadedAnnotations = []; /** @var int[] */ private $loadedFilemtimes = []; /** @param bool $debug */ public function __construct(Reader $reader, Cache $cache, $debug = false) { $this->delegate = $reader; $this->cache = $cache; $this->debug = (bool) $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === false) { $annots = $this->delegate->getClassAnnotations($class); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $cacheKey = $class->getName() . '$' . $property->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === false) { $annots = $this->delegate->getPropertyAnnotations($property); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $cacheKey = $class->getName() . '#' . $method->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class); if ($annots === false) { $annots = $this->delegate->getMethodAnnotations($method); $this->saveToCache($cacheKey, $annots); } return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * Clears loaded annotations. * * @return void */ public function clearLoadedAnnotations() { $this->loadedAnnotations = []; $this->loadedFilemtimes = []; } /** * Fetches a value from the cache. * * @param string $cacheKey The cache key. * * @return mixed The cached value or false when the value is not in cache. */ private function fetchFromCache($cacheKey, ReflectionClass $class) { $data = $this->cache->fetch($cacheKey); if ($data !== false) { if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { return $data; } } return false; } /** * Saves a value to the cache. * * @param string $cacheKey The cache key. * @param mixed $value The value. * * @return void */ private function saveToCache($cacheKey, $value) { $this->cache->save($cacheKey, $value); if (! $this->debug) { return; } $this->cache->save('[C]' . $cacheKey, time()); } /** * Checks if the cache is fresh. * * @param string $cacheKey * * @return bool */ private function isCacheFresh($cacheKey, ReflectionClass $class) { $lastModification = $this->getLastModification($class); if ($lastModification === 0) { return true; } return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; } /** * Returns the time the class was last modified, testing traits and parents */ private function getLastModification(ReflectionClass $class): int { $filename = $class->getFileName(); if (isset($this->loadedFilemtimes[$filename])) { return $this->loadedFilemtimes[$filename]; } $parent = $class->getParentClass(); $lastModification = max(array_merge( [$filename ? filemtime($filename) : 0], array_map(function (ReflectionClass $reflectionTrait): int { return $this->getTraitLastModificationTime($reflectionTrait); }, $class->getTraits()), array_map(function (ReflectionClass $class): int { return $this->getLastModification($class); }, $class->getInterfaces()), $parent ? [$this->getLastModification($parent)] : [] )); assert($lastModification !== false); return $this->loadedFilemtimes[$filename] = $lastModification; } private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int { $fileName = $reflectionTrait->getFileName(); if (isset($this->loadedFilemtimes[$fileName])) { return $this->loadedFilemtimes[$fileName]; } $lastModificationTime = max(array_merge( [$fileName ? filemtime($fileName) : 0], array_map(function (ReflectionClass $reflectionTrait): int { return $this->getTraitLastModificationTime($reflectionTrait); }, $reflectionTrait->getTraits()) )); assert($lastModificationTime !== false); return $this->loadedFilemtimes[$fileName] = $lastModificationTime; } } annotations/lib/Doctrine/Common/Annotations/DocLexer.php 0000644 00000007400 15120025740 0017356 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Doctrine\Common\Lexer\AbstractLexer; use function ctype_alpha; use function is_numeric; use function str_replace; use function stripos; use function strlen; use function strpos; use function strtolower; use function substr; /** * Simple lexer for docblock annotations. * * @template-extends AbstractLexer<DocLexer::T_*, string> */ final class DocLexer extends AbstractLexer { public const T_NONE = 1; public const T_INTEGER = 2; public const T_STRING = 3; public const T_FLOAT = 4; // All tokens that are also identifiers should be >= 100 public const T_IDENTIFIER = 100; public const T_AT = 101; public const T_CLOSE_CURLY_BRACES = 102; public const T_CLOSE_PARENTHESIS = 103; public const T_COMMA = 104; public const T_EQUALS = 105; public const T_FALSE = 106; public const T_NAMESPACE_SEPARATOR = 107; public const T_OPEN_CURLY_BRACES = 108; public const T_OPEN_PARENTHESIS = 109; public const T_TRUE = 110; public const T_NULL = 111; public const T_COLON = 112; public const T_MINUS = 113; /** @var array<string, self::T*> */ protected $noCase = [ '@' => self::T_AT, ',' => self::T_COMMA, '(' => self::T_OPEN_PARENTHESIS, ')' => self::T_CLOSE_PARENTHESIS, '{' => self::T_OPEN_CURLY_BRACES, '}' => self::T_CLOSE_CURLY_BRACES, '=' => self::T_EQUALS, ':' => self::T_COLON, '-' => self::T_MINUS, '\\' => self::T_NAMESPACE_SEPARATOR, ]; /** @var array<string, self::T*> */ protected $withCase = [ 'true' => self::T_TRUE, 'false' => self::T_FALSE, 'null' => self::T_NULL, ]; /** * Whether the next token starts immediately, or if there were * non-captured symbols before that */ public function nextTokenIsAdjacent(): bool { return $this->token === null || ($this->lookahead !== null && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value'])); } /** * {@inheritdoc} */ protected function getCatchablePatterns() { return [ '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*', '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', '"(?:""|[^"])*+"', ]; } /** * {@inheritdoc} */ protected function getNonCatchablePatterns() { return ['\s+', '\*+', '(.)']; } /** * {@inheritdoc} */ protected function getType(&$value) { $type = self::T_NONE; if ($value[0] === '"') { $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); return self::T_STRING; } if (isset($this->noCase[$value])) { return $this->noCase[$value]; } if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { return self::T_IDENTIFIER; } $lowerValue = strtolower($value); if (isset($this->withCase[$lowerValue])) { return $this->withCase[$lowerValue]; } // Checking numeric value if (is_numeric($value)) { return strpos($value, '.') !== false || stripos($value, 'e') !== false ? self::T_FLOAT : self::T_INTEGER; } return $type; } /** @return array{value: int|string, type:self::T_*|null, position:int} */ public function peek(): ?array { $token = parent::peek(); if ($token === null) { return null; } return (array) $token; } } annotations/lib/Doctrine/Common/Annotations/DocParser.php 0000644 00000141624 15120025740 0017542 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Doctrine\Common\Annotations\Annotation\Attribute; use Doctrine\Common\Annotations\Annotation\Attributes; use Doctrine\Common\Annotations\Annotation\Enum; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\Common\Annotations\Annotation\Target; use ReflectionClass; use ReflectionException; use ReflectionProperty; use RuntimeException; use stdClass; use Throwable; use function array_keys; use function array_map; use function array_pop; use function array_values; use function class_exists; use function constant; use function count; use function defined; use function explode; use function gettype; use function implode; use function in_array; use function interface_exists; use function is_array; use function is_object; use function json_encode; use function ltrim; use function preg_match; use function reset; use function rtrim; use function sprintf; use function stripos; use function strlen; use function strpos; use function strrpos; use function strtolower; use function substr; use function trim; use const PHP_VERSION_ID; /** * A parser for docblock annotations. * * It is strongly discouraged to change the default annotation parsing process. */ final class DocParser { /** * An array of all valid tokens for a class name. * * @phpstan-var list<int> */ private static $classIdentifiers = [ DocLexer::T_IDENTIFIER, DocLexer::T_TRUE, DocLexer::T_FALSE, DocLexer::T_NULL, ]; /** * The lexer. * * @var DocLexer */ private $lexer; /** * Current target context. * * @var int */ private $target; /** * Doc parser used to collect annotation target. * * @var DocParser */ private static $metadataParser; /** * Flag to control if the current annotation is nested or not. * * @var bool */ private $isNestedAnnotation = false; /** * Hashmap containing all use-statements that are to be used when parsing * the given doc block. * * @var array<string, class-string> */ private $imports = []; /** * This hashmap is used internally to cache results of class_exists() * look-ups. * * @var array<class-string, bool> */ private $classExists = []; /** * Whether annotations that have not been imported should be ignored. * * @var bool */ private $ignoreNotImportedAnnotations = false; /** * An array of default namespaces if operating in simple mode. * * @var string[] */ private $namespaces = []; /** * A list with annotations that are not causing exceptions when not resolved to an annotation class. * * The names must be the raw names as used in the class, not the fully qualified * * @var bool[] indexed by annotation name */ private $ignoredAnnotationNames = []; /** * A list with annotations in namespaced format * that are not causing exceptions when not resolved to an annotation class. * * @var bool[] indexed by namespace name */ private $ignoredAnnotationNamespaces = []; /** @var string */ private $context = ''; /** * Hash-map for caching annotation metadata. * * @var array<class-string, mixed[]> */ private static $annotationMetadata = [ Annotation\Target::class => [ 'is_annotation' => true, 'has_constructor' => true, 'has_named_argument_constructor' => false, 'properties' => [], 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'attribute_types' => [ 'value' => [ 'required' => false, 'type' => 'array', 'array_type' => 'string', 'value' => 'array<string>', ], ], ], Annotation\Attribute::class => [ 'is_annotation' => true, 'has_constructor' => false, 'has_named_argument_constructor' => false, 'targets_literal' => 'ANNOTATION_ANNOTATION', 'targets' => Target::TARGET_ANNOTATION, 'default_property' => 'name', 'properties' => [ 'name' => 'name', 'type' => 'type', 'required' => 'required', ], 'attribute_types' => [ 'value' => [ 'required' => true, 'type' => 'string', 'value' => 'string', ], 'type' => [ 'required' => true, 'type' => 'string', 'value' => 'string', ], 'required' => [ 'required' => false, 'type' => 'boolean', 'value' => 'boolean', ], ], ], Annotation\Attributes::class => [ 'is_annotation' => true, 'has_constructor' => false, 'has_named_argument_constructor' => false, 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => 'value', 'properties' => ['value' => 'value'], 'attribute_types' => [ 'value' => [ 'type' => 'array', 'required' => true, 'array_type' => Annotation\Attribute::class, 'value' => 'array<' . Annotation\Attribute::class . '>', ], ], ], Annotation\Enum::class => [ 'is_annotation' => true, 'has_constructor' => true, 'has_named_argument_constructor' => false, 'targets_literal' => 'ANNOTATION_PROPERTY', 'targets' => Target::TARGET_PROPERTY, 'default_property' => 'value', 'properties' => ['value' => 'value'], 'attribute_types' => [ 'value' => [ 'type' => 'array', 'required' => true, ], 'literal' => [ 'type' => 'array', 'required' => false, ], ], ], Annotation\NamedArgumentConstructor::class => [ 'is_annotation' => true, 'has_constructor' => false, 'has_named_argument_constructor' => false, 'targets_literal' => 'ANNOTATION_CLASS', 'targets' => Target::TARGET_CLASS, 'default_property' => null, 'properties' => [], 'attribute_types' => [], ], ]; /** * Hash-map for handle types declaration. * * @var array<string, string> */ private static $typeMap = [ 'float' => 'double', 'bool' => 'boolean', // allow uppercase Boolean in honor of George Boole 'Boolean' => 'boolean', 'int' => 'integer', ]; /** * Constructs a new DocParser. */ public function __construct() { $this->lexer = new DocLexer(); } /** * Sets the annotation names that are ignored during the parsing process. * * The names are supposed to be the raw names as used in the class, not the * fully qualified class names. * * @param bool[] $names indexed by annotation name * * @return void */ public function setIgnoredAnnotationNames(array $names) { $this->ignoredAnnotationNames = $names; } /** * Sets the annotation namespaces that are ignored during the parsing process. * * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name * * @return void */ public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces) { $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces; } /** * Sets ignore on not-imported annotations. * * @param bool $bool * * @return void */ public function setIgnoreNotImportedAnnotations($bool) { $this->ignoreNotImportedAnnotations = (bool) $bool; } /** * Sets the default namespaces. * * @param string $namespace * * @return void * * @throws RuntimeException */ public function addNamespace($namespace) { if ($this->imports) { throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->namespaces[] = $namespace; } /** * Sets the imports. * * @param array<string, class-string> $imports * * @return void * * @throws RuntimeException */ public function setImports(array $imports) { if ($this->namespaces) { throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); } $this->imports = $imports; } /** * Sets current target context as bitmask. * * @param int $target * * @return void */ public function setTarget($target) { $this->target = $target; } /** * Parses the given docblock string for annotations. * * @param string $input The docblock string to parse. * @param string $context The parsing context. * * @phpstan-return list<object> Array of annotations. If no annotations are found, an empty array is returned. * * @throws AnnotationException * @throws ReflectionException */ public function parse($input, $context = '') { $pos = $this->findInitialTokenPosition($input); if ($pos === null) { return []; } $this->context = $context; $this->lexer->setInput(trim(substr($input, $pos), '* /')); $this->lexer->moveNext(); return $this->Annotations(); } /** * Finds the first valid annotation * * @param string $input The docblock string to parse */ private function findInitialTokenPosition($input): ?int { $pos = 0; // search for first valid annotation while (($pos = strpos($input, '@', $pos)) !== false) { $preceding = substr($input, $pos - 1, 1); // if the @ is preceded by a space, a tab or * it is valid if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") { return $pos; } $pos++; } return null; } /** * Attempts to match the given token with the current lookahead token. * If they match, updates the lookahead token; otherwise raises a syntax error. * * @param int $token Type of token. * * @return bool True if tokens match; false otherwise. * * @throws AnnotationException */ private function match(int $token): bool { if (! $this->lexer->isNextToken($token)) { throw $this->syntaxError($this->lexer->getLiteral($token)); } return $this->lexer->moveNext(); } /** * Attempts to match the current lookahead token with any of the given tokens. * * If any of them matches, this method updates the lookahead token; otherwise * a syntax error is raised. * * @phpstan-param list<mixed[]> $tokens * * @throws AnnotationException */ private function matchAny(array $tokens): bool { if (! $this->lexer->isNextTokenAny($tokens)) { throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens))); } return $this->lexer->moveNext(); } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param mixed[]|null $token Optional token. */ private function syntaxError(string $expected, ?array $token = null): AnnotationException { if ($token === null) { $token = $this->lexer->lookahead; } $message = sprintf('Expected %s, got ', $expected); $message .= $this->lexer->lookahead === null ? 'end of string' : sprintf("'%s' at position %s", $token['value'], $token['position']); if (strlen($this->context)) { $message .= ' in ' . $this->context; } $message .= '.'; return AnnotationException::syntaxError($message); } /** * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism * but uses the {@link AnnotationRegistry} to load classes. * * @param class-string $fqcn */ private function classExists(string $fqcn): bool { if (isset($this->classExists[$fqcn])) { return $this->classExists[$fqcn]; } // first check if the class already exists, maybe loaded through another AnnotationReader if (class_exists($fqcn, false)) { return $this->classExists[$fqcn] = true; } // final check, does this class exist? return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); } /** * Collects parsing metadata for a given annotation class * * @param class-string $name The annotation name * * @throws AnnotationException * @throws ReflectionException */ private function collectAnnotationMetadata(string $name): void { if (self::$metadataParser === null) { self::$metadataParser = new self(); self::$metadataParser->setIgnoreNotImportedAnnotations(true); self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); self::$metadataParser->setImports([ 'enum' => Enum::class, 'target' => Target::class, 'attribute' => Attribute::class, 'attributes' => Attributes::class, 'namedargumentconstructor' => NamedArgumentConstructor::class, ]); // Make sure that annotations from metadata are loaded class_exists(Enum::class); class_exists(Target::class); class_exists(Attribute::class); class_exists(Attributes::class); class_exists(NamedArgumentConstructor::class); } $class = new ReflectionClass($name); $docComment = $class->getDocComment(); // Sets default values for annotation metadata $constructor = $class->getConstructor(); $metadata = [ 'default_property' => null, 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0, 'constructor_args' => [], 'properties' => [], 'property_types' => [], 'attribute_types' => [], 'targets_literal' => null, 'targets' => Target::TARGET_ALL, 'is_annotation' => strpos($docComment, '@Annotation') !== false, ]; $metadata['has_named_argument_constructor'] = $metadata['has_constructor'] && $class->implementsInterface(NamedArgumentConstructorAnnotation::class); // verify that the class is really meant to be an annotation if ($metadata['is_annotation']) { self::$metadataParser->setTarget(Target::TARGET_CLASS); foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { if ($annotation instanceof Target) { $metadata['targets'] = $annotation->targets; $metadata['targets_literal'] = $annotation->literal; continue; } if ($annotation instanceof NamedArgumentConstructor) { $metadata['has_named_argument_constructor'] = $metadata['has_constructor']; if ($metadata['has_named_argument_constructor']) { // choose the first argument as the default property $metadata['default_property'] = $constructor->getParameters()[0]->getName(); } } if (! ($annotation instanceof Attributes)) { continue; } foreach ($annotation->value as $attribute) { $this->collectAttributeTypeMetadata($metadata, $attribute); } } // if not has a constructor will inject values into public properties if ($metadata['has_constructor'] === false) { // collect all public properties foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $metadata['properties'][$property->name] = $property->name; $propertyComment = $property->getDocComment(); if ($propertyComment === false) { continue; } $attribute = new Attribute(); $attribute->required = (strpos($propertyComment, '@Required') !== false); $attribute->name = $property->name; $attribute->type = (strpos($propertyComment, '@var') !== false && preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches)) ? $matches[1] : 'mixed'; $this->collectAttributeTypeMetadata($metadata, $attribute); // checks if the property has @Enum if (strpos($propertyComment, '@Enum') === false) { continue; } $context = 'property ' . $class->name . '::$' . $property->name; self::$metadataParser->setTarget(Target::TARGET_PROPERTY); foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { if (! $annotation instanceof Enum) { continue; } $metadata['enum'][$property->name]['value'] = $annotation->value; $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal)) ? $annotation->literal : $annotation->value; } } // choose the first property as default property $metadata['default_property'] = reset($metadata['properties']); } elseif ($metadata['has_named_argument_constructor']) { foreach ($constructor->getParameters() as $parameter) { if ($parameter->isVariadic()) { break; } $metadata['constructor_args'][$parameter->getName()] = [ 'position' => $parameter->getPosition(), 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null, ]; } } } self::$annotationMetadata[$name] = $metadata; } /** * Collects parsing metadata for a given attribute. * * @param mixed[] $metadata */ private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void { // handle internal type declaration $type = self::$typeMap[$attribute->type] ?? $attribute->type; // handle the case if the property type is mixed if ($type === 'mixed') { return; } // Evaluate type $pos = strpos($type, '<'); if ($pos !== false) { // Checks if the property has array<type> $arrayType = substr($type, $pos + 1, -1); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; } else { // Checks if the property has type[] $pos = strrpos($type, '['); if ($pos !== false) { $arrayType = substr($type, 0, $pos); $type = 'array'; if (isset(self::$typeMap[$arrayType])) { $arrayType = self::$typeMap[$arrayType]; } $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType; } } $metadata['attribute_types'][$attribute->name]['type'] = $type; $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type; $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required; } /** * Annotations ::= Annotation {[ "*" ]* [Annotation]}* * * @phpstan-return list<object> * * @throws AnnotationException * @throws ReflectionException */ private function Annotations(): array { $annotations = []; while ($this->lexer->lookahead !== null) { if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) { $this->lexer->moveNext(); continue; } // make sure the @ is preceded by non-catchable pattern if ( $this->lexer->token !== null && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen( $this->lexer->token['value'] ) ) { $this->lexer->moveNext(); continue; } // make sure the @ is followed by either a namespace separator, or // an identifier token $peek = $this->lexer->glimpse(); if ( ($peek === null) || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array( $peek['type'], self::$classIdentifiers, true )) || $peek['position'] !== $this->lexer->lookahead['position'] + 1 ) { $this->lexer->moveNext(); continue; } $this->isNestedAnnotation = false; $annot = $this->Annotation(); if ($annot === false) { continue; } $annotations[] = $annot; } return $annotations; } /** * Annotation ::= "@" AnnotationName MethodCall * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return object|false False if it is not a valid annotation. * * @throws AnnotationException * @throws ReflectionException */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation $name = $this->Identifier(); if ( $this->lexer->isNextToken(DocLexer::T_MINUS) && $this->lexer->nextTokenIsAdjacent() ) { // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded return false; } // only process names which are not fully qualified, yet // fully qualified names must start with a \ $originalName = $name; if ($name[0] !== '\\') { $pos = strpos($name, '\\'); $alias = ($pos === false) ? $name : substr($name, 0, $pos); $found = false; $loweredAlias = strtolower($alias); if ($this->namespaces) { foreach ($this->namespaces as $namespace) { if ($this->classExists($namespace . '\\' . $name)) { $name = $namespace . '\\' . $name; $found = true; break; } } } elseif (isset($this->imports[$loweredAlias])) { $namespace = ltrim($this->imports[$loweredAlias], '\\'); $name = ($pos !== false) ? $namespace . substr($name, $pos) : $namespace; $found = $this->classExists($name); } elseif ( ! isset($this->ignoredAnnotationNames[$name]) && isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name) ) { $name = $this->imports['__NAMESPACE__'] . '\\' . $name; $found = true; } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) { $found = true; } if (! $found) { if ($this->isIgnoredAnnotation($name)) { return false; } throw AnnotationException::semanticalError(sprintf( <<<'EXCEPTION' The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation? EXCEPTION , $name, $this->context )); } } $name = ltrim($name, '\\'); if (! $this->classExists($name)) { throw AnnotationException::semanticalError(sprintf( 'The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context )); } // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // collects the metadata annotation only if there is not yet if (! isset(self::$annotationMetadata[$name])) { $this->collectAnnotationMetadata($name); } // verify that the class is really meant to be an annotation and not just any ordinary class if (self::$annotationMetadata[$name]['is_annotation'] === false) { if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) { return false; } throw AnnotationException::semanticalError(sprintf( <<<'EXCEPTION' The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s. EXCEPTION , $name, $name, $originalName, $this->context )); } //if target is nested annotation $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; // Next will be nested $this->isNestedAnnotation = true; //if annotation does not support current target if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) { throw AnnotationException::semanticalError( sprintf( <<<'EXCEPTION' Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s. EXCEPTION , $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'] ) ); } $arguments = $this->MethodCall(); $values = $this->resolvePositionalValues($arguments, $name); if (isset(self::$annotationMetadata[$name]['enum'])) { // checks all declared attributes foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { // checks if the attribute is a valid enumerator if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { throw AnnotationException::enumeratorError( $property, $name, $this->context, $enum['literal'], $values[$property] ); } } } // checks all declared attributes foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { if ( $property === self::$annotationMetadata[$name]['default_property'] && ! isset($values[$property]) && isset($values['value']) ) { $property = 'value'; } // handle a not given attribute or null value if (! isset($values[$property])) { if ($type['required']) { throw AnnotationException::requiredError( $property, $originalName, $this->context, 'a(n) ' . $type['value'] ); } continue; } if ($type['type'] === 'array') { // handle the case of a single value if (! is_array($values[$property])) { $values[$property] = [$values[$property]]; } // checks if the attribute has array type declaration, such as "array<string>" if (isset($type['array_type'])) { foreach ($values[$property] as $item) { if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) { throw AnnotationException::attributeTypeError( $property, $originalName, $this->context, 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', $item ); } } } } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) { throw AnnotationException::attributeTypeError( $property, $originalName, $this->context, 'a(n) ' . $type['value'], $values[$property] ); } } if (self::$annotationMetadata[$name]['has_named_argument_constructor']) { if (PHP_VERSION_ID >= 80000) { foreach ($values as $property => $value) { if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { throw AnnotationException::creationError(sprintf( <<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s" that can be set through its named arguments constructor. Available named arguments: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])) )); } } return $this->instantiateAnnotiation($originalName, $this->context, $name, $values); } $positionalValues = []; foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { $positionalValues[$parameter['position']] = $parameter['default']; } foreach ($values as $property => $value) { if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) { throw AnnotationException::creationError(sprintf( <<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s" that can be set through its named arguments constructor. Available named arguments: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args'])) )); } $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value; } return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues); } // check if the annotation expects values via the constructor, // or directly injected into public properties if (self::$annotationMetadata[$name]['has_constructor'] === true) { return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]); } $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []); foreach ($values as $property => $value) { if (! isset(self::$annotationMetadata[$name]['properties'][$property])) { if ($property !== 'value') { throw AnnotationException::creationError(sprintf( <<<'EXCEPTION' The annotation @%s declared on %s does not have a property named "%s". Available properties: %s EXCEPTION , $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']) )); } // handle the case if the property has no annotations $property = self::$annotationMetadata[$name]['default_property']; if (! $property) { throw AnnotationException::creationError(sprintf( 'The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values) )); } } $instance->{$property} = $value; } return $instance; } /** * MethodCall ::= ["(" [Values] ")"] * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function MethodCall(): array { $values = []; if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { return $values; } $this->match(DocLexer::T_OPEN_PARENTHESIS); if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); return $values; } /** * Values ::= Array | Value {"," Value}* [","] * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function Values(): array { $values = [$this->Value()]; while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { break; } $token = $this->lexer->lookahead; $value = $this->Value(); $values[] = $value; } $namedArguments = []; $positionalArguments = []; foreach ($values as $k => $value) { if (is_object($value) && $value instanceof stdClass) { $namedArguments[$value->name] = $value->value; } else { $positionalArguments[$k] = $value; } } return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments]; } /** * Constant ::= integer | string | float | boolean * * @return mixed * * @throws AnnotationException */ private function Constant() { $identifier = $this->Identifier(); if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') { [$className, $const] = explode('::', $identifier); $pos = strpos($className, '\\'); $alias = ($pos === false) ? $className : substr($className, 0, $pos); $found = false; $loweredAlias = strtolower($alias); switch (true) { case ! empty($this->namespaces): foreach ($this->namespaces as $ns) { if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { $className = $ns . '\\' . $className; $found = true; break; } } break; case isset($this->imports[$loweredAlias]): $found = true; $className = ($pos !== false) ? $this->imports[$loweredAlias] . substr($className, $pos) : $this->imports[$loweredAlias]; break; default: if (isset($this->imports['__NAMESPACE__'])) { $ns = $this->imports['__NAMESPACE__']; if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) { $className = $ns . '\\' . $className; $found = true; } } break; } if ($found) { $identifier = $className . '::' . $const; } } /** * Checks if identifier ends with ::class and remove the leading backslash if it exists. */ if ( $this->identifierEndsWithClassConstant($identifier) && ! $this->identifierStartsWithBackslash($identifier) ) { return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier)); } if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) { return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1); } if (! defined($identifier)) { throw AnnotationException::semanticalErrorConstants($identifier, $this->context); } return constant($identifier); } private function identifierStartsWithBackslash(string $identifier): bool { return $identifier[0] === '\\'; } private function identifierEndsWithClassConstant(string $identifier): bool { return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class'); } /** @return int|false */ private function getClassConstantPositionInIdentifier(string $identifier) { return stripos($identifier, '::class'); } /** * Identifier ::= string * * @throws AnnotationException */ private function Identifier(): string { // check if we have an annotation if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) { throw $this->syntaxError('namespace separator or identifier'); } $this->lexer->moveNext(); $className = $this->lexer->token['value']; while ( $this->lexer->lookahead !== null && $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR) ) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $className .= '\\' . $this->lexer->token['value']; } return $className; } /** * Value ::= PlainValue | FieldAssignment * * @return mixed * * @throws AnnotationException * @throws ReflectionException */ private function Value() { $peek = $this->lexer->glimpse(); if ($peek['type'] === DocLexer::T_EQUALS) { return $this->FieldAssignment(); } return $this->PlainValue(); } /** * PlainValue ::= integer | string | float | boolean | Array | Annotation * * @return mixed * * @throws AnnotationException * @throws ReflectionException */ private function PlainValue() { if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { return $this->Arrayx(); } if ($this->lexer->isNextToken(DocLexer::T_AT)) { return $this->Annotation(); } if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { return $this->Constant(); } switch ($this->lexer->lookahead['type']) { case DocLexer::T_STRING: $this->match(DocLexer::T_STRING); return $this->lexer->token['value']; case DocLexer::T_INTEGER: $this->match(DocLexer::T_INTEGER); return (int) $this->lexer->token['value']; case DocLexer::T_FLOAT: $this->match(DocLexer::T_FLOAT); return (float) $this->lexer->token['value']; case DocLexer::T_TRUE: $this->match(DocLexer::T_TRUE); return true; case DocLexer::T_FALSE: $this->match(DocLexer::T_FALSE); return false; case DocLexer::T_NULL: $this->match(DocLexer::T_NULL); return null; default: throw $this->syntaxError('PlainValue'); } } /** * FieldAssignment ::= FieldName "=" PlainValue * FieldName ::= identifier * * @throws AnnotationException * @throws ReflectionException */ private function FieldAssignment(): stdClass { $this->match(DocLexer::T_IDENTIFIER); $fieldName = $this->lexer->token['value']; $this->match(DocLexer::T_EQUALS); $item = new stdClass(); $item->name = $fieldName; $item->value = $this->PlainValue(); return $item; } /** * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" * * @return mixed[] * * @throws AnnotationException * @throws ReflectionException */ private function Arrayx(): array { $array = $values = []; $this->match(DocLexer::T_OPEN_CURLY_BRACES); // If the array is empty, stop parsing and return. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { $this->match(DocLexer::T_CLOSE_CURLY_BRACES); return $array; } $values[] = $this->ArrayEntry(); while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { $this->match(DocLexer::T_COMMA); // optional trailing comma if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { break; } $values[] = $this->ArrayEntry(); } $this->match(DocLexer::T_CLOSE_CURLY_BRACES); foreach ($values as $value) { [$key, $val] = $value; if ($key !== null) { $array[$key] = $val; } else { $array[] = $val; } } return $array; } /** * ArrayEntry ::= Value | KeyValuePair * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant * Key ::= string | integer | Constant * * @phpstan-return array{mixed, mixed} * * @throws AnnotationException * @throws ReflectionException */ private function ArrayEntry(): array { $peek = $this->lexer->glimpse(); if ( $peek['type'] === DocLexer::T_EQUALS || $peek['type'] === DocLexer::T_COLON ) { if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { $key = $this->Constant(); } else { $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]); $key = $this->lexer->token['value']; } $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]); return [$key, $this->PlainValue()]; } return [null, $this->Value()]; } /** * Checks whether the given $name matches any ignored annotation name or namespace */ private function isIgnoredAnnotation(string $name): bool { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return true; } foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) { $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\'; if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) { return true; } } return false; } /** * Resolve positional arguments (without name) to named ones * * @param array<string,mixed> $arguments * * @return array<string,mixed> */ private function resolvePositionalValues(array $arguments, string $name): array { $positionalArguments = $arguments['positional_arguments'] ?? []; $values = $arguments['named_arguments'] ?? []; if ( self::$annotationMetadata[$name]['has_named_argument_constructor'] && self::$annotationMetadata[$name]['default_property'] !== null ) { // We must ensure that we don't have positional arguments after named ones $positions = array_keys($positionalArguments); $lastPosition = null; foreach ($positions as $position) { if ( ($lastPosition === null && $position !== 0) || ($lastPosition !== null && $position !== $lastPosition + 1) ) { throw $this->syntaxError('Positional arguments after named arguments is not allowed'); } $lastPosition = $position; } foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) { $position = $parameter['position']; if (isset($values[$property]) || ! isset($positionalArguments[$position])) { continue; } $values[$property] = $positionalArguments[$position]; } } else { if (count($positionalArguments) > 0 && ! isset($values['value'])) { if (count($positionalArguments) === 1) { $value = array_pop($positionalArguments); } else { $value = array_values($positionalArguments); } $values['value'] = $value; } } return $values; } /** * Try to instantiate the annotation and catch and process any exceptions related to failure * * @param class-string $name * @param array<string,mixed> $arguments * * @return object * * @throws AnnotationException */ private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments) { try { return new $name(...$arguments); } catch (Throwable $exception) { throw AnnotationException::creationError( sprintf( 'An error occurred while instantiating the annotation @%s declared on %s: "%s".', $originalName, $context, $exception->getMessage() ), $exception ); } } } annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php 0000644 00000020626 15120025740 0020604 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use InvalidArgumentException; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use RuntimeException; use function chmod; use function file_put_contents; use function filemtime; use function gettype; use function is_dir; use function is_file; use function is_int; use function is_writable; use function mkdir; use function rename; use function rtrim; use function serialize; use function sha1; use function sprintf; use function strtr; use function tempnam; use function uniqid; use function unlink; use function var_export; /** * File cache reader for annotations. * * @deprecated the FileCacheReader is deprecated and will be removed * in version 2.0.0 of doctrine/annotations. Please use the * {@see \Doctrine\Common\Annotations\PsrCachedReader} instead. */ class FileCacheReader implements Reader { /** @var Reader */ private $reader; /** @var string */ private $dir; /** @var bool */ private $debug; /** @phpstan-var array<string, list<object>> */ private $loadedAnnotations = []; /** @var array<string, string> */ private $classNameHashes = []; /** @var int */ private $umask; /** * @param string $cacheDir * @param bool $debug * @param int $umask * * @throws InvalidArgumentException */ public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002) { if (! is_int($umask)) { throw new InvalidArgumentException(sprintf( 'The parameter umask must be an integer, was: %s', gettype($umask) )); } $this->reader = $reader; $this->umask = $umask; if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) { throw new InvalidArgumentException(sprintf( 'The directory "%s" does not exist and could not be created.', $cacheDir )); } $this->dir = rtrim($cacheDir, '\\/'); $this->debug = $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { if (! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name]; if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (! is_file($path)) { $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ( $this->debug && $filename !== false && filemtime($path) < filemtime($filename) ) { @unlink($path); $annot = $this->reader->getClassAnnotations($class); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); if (! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name] . '$' . $property->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (! is_file($path)) { $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ( $this->debug && $filename !== false && filemtime($path) < filemtime($filename) ) { @unlink($path); $annot = $this->reader->getPropertyAnnotations($property); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); if (! isset($this->classNameHashes[$class->name])) { $this->classNameHashes[$class->name] = sha1($class->name); } $key = $this->classNameHashes[$class->name] . '#' . $method->getName(); if (isset($this->loadedAnnotations[$key])) { return $this->loadedAnnotations[$key]; } $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php'; if (! is_file($path)) { $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } $filename = $class->getFilename(); if ( $this->debug && $filename !== false && filemtime($path) < filemtime($filename) ) { @unlink($path); $annot = $this->reader->getMethodAnnotations($method); $this->saveCacheFile($path, $annot); return $this->loadedAnnotations[$key] = $annot; } return $this->loadedAnnotations[$key] = include $path; } /** * Saves the cache file. * * @param string $path * @param mixed $data * * @return void */ private function saveCacheFile($path, $data) { if (! is_writable($this->dir)) { throw new InvalidArgumentException(sprintf( <<<'EXCEPTION' The directory "%s" is not writable. Both the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package., EXCEPTION , $this->dir )); } $tempfile = tempnam($this->dir, uniqid('', true)); if ($tempfile === false) { throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir)); } @chmod($tempfile, 0666 & (~$this->umask)); $written = file_put_contents( $tempfile, '<?php return unserialize(' . var_export(serialize($data), true) . ');' ); if ($written === false) { throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile)); } @chmod($tempfile, 0666 & (~$this->umask)); if (rename($tempfile, $path) === false) { @unlink($tempfile); throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path)); } } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { $annotations = $this->getClassAnnotations($class); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { $annotations = $this->getMethodAnnotations($method); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { $annotations = $this->getPropertyAnnotations($property); foreach ($annotations as $annotation) { if ($annotation instanceof $annotationName) { return $annotation; } } return null; } /** * Clears loaded annotations. * * @return void */ public function clearLoadedAnnotations() { $this->loadedAnnotations = []; } } annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php 0000644 00000012655 15120025740 0024327 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Common\Annotations; /** * A list of annotations that are implicitly ignored during the parsing process. * * All names are case sensitive. */ final class ImplicitlyIgnoredAnnotationNames { private const Reserved = [ 'Annotation' => true, 'Attribute' => true, 'Attributes' => true, /* Can we enable this? 'Enum' => true, */ 'Required' => true, 'Target' => true, 'NamedArgumentConstructor' => true, ]; private const WidelyUsedNonStandard = [ 'fix' => true, 'fixme' => true, 'override' => true, ]; private const PhpDocumentor1 = [ 'abstract' => true, 'access' => true, 'code' => true, 'deprec' => true, 'endcode' => true, 'exception' => true, 'final' => true, 'ingroup' => true, 'inheritdoc' => true, 'inheritDoc' => true, 'magic' => true, 'name' => true, 'private' => true, 'static' => true, 'staticvar' => true, 'staticVar' => true, 'toc' => true, 'tutorial' => true, 'throw' => true, ]; private const PhpDocumentor2 = [ 'api' => true, 'author' => true, 'category' => true, 'copyright' => true, 'deprecated' => true, 'example' => true, 'filesource' => true, 'global' => true, 'ignore' => true, /* Can we enable this? 'index' => true, */ 'internal' => true, 'license' => true, 'link' => true, 'method' => true, 'package' => true, 'param' => true, 'property' => true, 'property-read' => true, 'property-write' => true, 'return' => true, 'see' => true, 'since' => true, 'source' => true, 'subpackage' => true, 'throws' => true, 'todo' => true, 'TODO' => true, 'usedby' => true, 'uses' => true, 'var' => true, 'version' => true, ]; private const PHPUnit = [ 'author' => true, 'after' => true, 'afterClass' => true, 'backupGlobals' => true, 'backupStaticAttributes' => true, 'before' => true, 'beforeClass' => true, 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, 'covers' => true, 'coversDefaultClass' => true, 'coversNothing' => true, 'dataProvider' => true, 'depends' => true, 'doesNotPerformAssertions' => true, 'expectedException' => true, 'expectedExceptionCode' => true, 'expectedExceptionMessage' => true, 'expectedExceptionMessageRegExp' => true, 'group' => true, 'large' => true, 'medium' => true, 'preserveGlobalState' => true, 'requires' => true, 'runTestsInSeparateProcesses' => true, 'runInSeparateProcess' => true, 'small' => true, 'test' => true, 'testdox' => true, 'testWith' => true, 'ticket' => true, 'uses' => true, ]; private const PhpCheckStyle = ['SuppressWarnings' => true]; private const PhpStorm = ['noinspection' => true]; private const PEAR = ['package_version' => true]; private const PlainUML = [ 'startuml' => true, 'enduml' => true, ]; private const Symfony = ['experimental' => true]; private const PhpCodeSniffer = [ 'codingStandardsIgnoreStart' => true, 'codingStandardsIgnoreEnd' => true, ]; private const SlevomatCodingStandard = ['phpcsSuppress' => true]; private const Phan = ['suppress' => true]; private const Rector = ['noRector' => true]; private const StaticAnalysis = [ // PHPStan, Psalm 'extends' => true, 'implements' => true, 'readonly' => true, 'template' => true, 'use' => true, // Psalm 'pure' => true, 'immutable' => true, ]; public const LIST = self::Reserved + self::WidelyUsedNonStandard + self::PhpDocumentor1 + self::PhpDocumentor2 + self::PHPUnit + self::PhpCheckStyle + self::PhpStorm + self::PEAR + self::PlainUML + self::Symfony + self::SlevomatCodingStandard + self::PhpCodeSniffer + self::Phan + self::Rector + self::StaticAnalysis; private function __construct() { } } annotations/lib/Doctrine/Common/Annotations/IndexedReader.php 0000644 00000004376 15120025740 0020365 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use function call_user_func_array; use function get_class; /** * Allows the reader to be used in-place of Doctrine's reader. */ class IndexedReader implements Reader { /** @var Reader */ private $delegate; public function __construct(Reader $reader) { $this->delegate = $reader; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $annotations = []; foreach ($this->delegate->getClassAnnotations($class) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { return $this->delegate->getClassAnnotation($class, $annotationName); } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $annotations = []; foreach ($this->delegate->getMethodAnnotations($method) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { return $this->delegate->getMethodAnnotation($method, $annotationName); } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $annotations = []; foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { $annotations[get_class($annot)] = $annot; } return $annotations; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { return $this->delegate->getPropertyAnnotation($property, $annotationName); } /** * Proxies all methods to the delegate. * * @param string $method * @param mixed[] $args * * @return mixed */ public function __call($method, $args) { return call_user_func_array([$this->delegate, $method], $args); } } annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php 0000644 00000000532 15120025740 0024700 0 ustar 00 <?php namespace Doctrine\Common\Annotations; /** * Marker interface for PHP7/PHP8 compatible support * for named arguments (and constructor property promotion). * * @deprecated Implementing this interface is deprecated * Use the Annotation @NamedArgumentConstructor instead */ interface NamedArgumentConstructorAnnotation { } annotations/lib/Doctrine/Common/Annotations/PhpParser.php 0000644 00000004661 15120025740 0017563 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use ReflectionClass; use ReflectionFunction; use SplFileObject; use function is_file; use function method_exists; use function preg_quote; use function preg_replace; /** * Parses a file for namespaces/use/class declarations. */ final class PhpParser { /** * Parses a class. * * @deprecated use parseUseStatements instead * * @param ReflectionClass $class A <code>ReflectionClass</code> object. * * @return array<string, class-string> A list with use statements in the form (Alias => FQN). */ public function parseClass(ReflectionClass $class) { return $this->parseUseStatements($class); } /** * Parse a class or function for use statements. * * @param ReflectionClass|ReflectionFunction $reflection * * @psalm-return array<string, string> a list with use statements in the form (Alias => FQN). */ public function parseUseStatements($reflection): array { if (method_exists($reflection, 'getUseStatements')) { return $reflection->getUseStatements(); } $filename = $reflection->getFileName(); if ($filename === false) { return []; } $content = $this->getFileContent($filename, $reflection->getStartLine()); if ($content === null) { return []; } $namespace = preg_quote($reflection->getNamespaceName()); $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); $tokenizer = new TokenParser('<?php ' . $content); return $tokenizer->parseUseStatements($reflection->getNamespaceName()); } /** * Gets the content of the file right up to the given line number. * * @param string $filename The name of the file to load. * @param int $lineNumber The number of lines to read from file. * * @return string|null The content of the file or null if the file does not exist. */ private function getFileContent($filename, $lineNumber) { if (! is_file($filename)) { return null; } $content = ''; $lineCnt = 0; $file = new SplFileObject($filename); while (! $file->eof()) { if ($lineCnt++ === $lineNumber) { break; } $content .= $file->fgets(); } return $content; } } annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php 0000644 00000014525 15120025740 0020636 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; use Reflector; use function array_map; use function array_merge; use function assert; use function filemtime; use function max; use function rawurlencode; use function time; /** * A cache aware annotation reader. */ final class PsrCachedReader implements Reader { /** @var Reader */ private $delegate; /** @var CacheItemPoolInterface */ private $cache; /** @var bool */ private $debug; /** @var array<string, array<object>> */ private $loadedAnnotations = []; /** @var int[] */ private $loadedFilemtimes = []; public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false) { $this->delegate = $reader; $this->cache = $cache; $this->debug = (bool) $debug; } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { $class = $property->getDeclaringClass(); $cacheKey = $class->getName() . '$' . $property->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { $class = $method->getDeclaringClass(); $cacheKey = $class->getName() . '#' . $method->getName(); if (isset($this->loadedAnnotations[$cacheKey])) { return $this->loadedAnnotations[$cacheKey]; } $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method); return $this->loadedAnnotations[$cacheKey] = $annots; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } public function clearLoadedAnnotations(): void { $this->loadedAnnotations = []; $this->loadedFilemtimes = []; } /** @return mixed[] */ private function fetchFromCache( string $cacheKey, ReflectionClass $class, string $method, Reflector $reflector ): array { $cacheKey = rawurlencode($cacheKey); $item = $this->cache->getItem($cacheKey); if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) { $this->cache->save($item->set($this->delegate->{$method}($reflector))); } return $item->get(); } /** * Used in debug mode to check if the cache is fresh. * * @return bool Returns true if the cache was fresh, or false if the class * being read was modified since writing to the cache. */ private function refresh(string $cacheKey, ReflectionClass $class): bool { $lastModification = $this->getLastModification($class); if ($lastModification === 0) { return true; } $item = $this->cache->getItem('[C]' . $cacheKey); if ($item->isHit() && $item->get() >= $lastModification) { return true; } $this->cache->save($item->set(time())); return false; } /** * Returns the time the class was last modified, testing traits and parents */ private function getLastModification(ReflectionClass $class): int { $filename = $class->getFileName(); if (isset($this->loadedFilemtimes[$filename])) { return $this->loadedFilemtimes[$filename]; } $parent = $class->getParentClass(); $lastModification = max(array_merge( [$filename ? filemtime($filename) : 0], array_map(function (ReflectionClass $reflectionTrait): int { return $this->getTraitLastModificationTime($reflectionTrait); }, $class->getTraits()), array_map(function (ReflectionClass $class): int { return $this->getLastModification($class); }, $class->getInterfaces()), $parent ? [$this->getLastModification($parent)] : [] )); assert($lastModification !== false); return $this->loadedFilemtimes[$filename] = $lastModification; } private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int { $fileName = $reflectionTrait->getFileName(); if (isset($this->loadedFilemtimes[$fileName])) { return $this->loadedFilemtimes[$fileName]; } $lastModificationTime = max(array_merge( [$fileName ? filemtime($fileName) : 0], array_map(function (ReflectionClass $reflectionTrait): int { return $this->getTraitLastModificationTime($reflectionTrait); }, $reflectionTrait->getTraits()) )); assert($lastModificationTime !== false); return $this->loadedFilemtimes[$fileName] = $lastModificationTime; } } annotations/lib/Doctrine/Common/Annotations/Reader.php 0000644 00000004752 15120025740 0017062 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; /** * Interface for annotation readers. */ interface Reader { /** * Gets the annotations applied to a class. * * @param ReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * * @return array<object> An array of Annotations. */ public function getClassAnnotations(ReflectionClass $class); /** * Gets a class annotation. * * @param ReflectionClass $class The ReflectionClass of the class from which * the class annotations should be read. * @param class-string<T> $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getClassAnnotation(ReflectionClass $class, $annotationName); /** * Gets the annotations applied to a method. * * @param ReflectionMethod $method The ReflectionMethod of the method from which * the annotations should be read. * * @return array<object> An array of Annotations. */ public function getMethodAnnotations(ReflectionMethod $method); /** * Gets a method annotation. * * @param ReflectionMethod $method The ReflectionMethod to read the annotations from. * @param class-string<T> $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName); /** * Gets the annotations applied to a property. * * @param ReflectionProperty $property The ReflectionProperty of the property * from which the annotations should be read. * * @return array<object> An array of Annotations. */ public function getPropertyAnnotations(ReflectionProperty $property); /** * Gets a property annotation. * * @param ReflectionProperty $property The ReflectionProperty to read the annotations from. * @param class-string<T> $annotationName The name of the annotation. * * @return T|null The Annotation or NULL, if the requested annotation does not exist. * * @template T */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName); } annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php 0000644 00000005241 15120025740 0022261 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use ReflectionClass; use ReflectionMethod; use ReflectionProperty; /** * Simple Annotation Reader. * * This annotation reader is intended to be used in projects where you have * full-control over all annotations that are available. * * @deprecated Deprecated in favour of using AnnotationReader */ class SimpleAnnotationReader implements Reader { /** @var DocParser */ private $parser; /** * Initializes a new SimpleAnnotationReader. */ public function __construct() { $this->parser = new DocParser(); $this->parser->setIgnoreNotImportedAnnotations(true); } /** * Adds a namespace in which we will look for annotations. * * @param string $namespace * * @return void */ public function addNamespace($namespace) { $this->parser->addNamespace($namespace); } /** * {@inheritDoc} */ public function getClassAnnotations(ReflectionClass $class) { return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); } /** * {@inheritDoc} */ public function getMethodAnnotations(ReflectionMethod $method) { return $this->parser->parse( $method->getDocComment(), 'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()' ); } /** * {@inheritDoc} */ public function getPropertyAnnotations(ReflectionProperty $property) { return $this->parser->parse( $property->getDocComment(), 'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName() ); } /** * {@inheritDoc} */ public function getClassAnnotation(ReflectionClass $class, $annotationName) { foreach ($this->getClassAnnotations($class) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getMethodAnnotation(ReflectionMethod $method, $annotationName) { foreach ($this->getMethodAnnotations($method) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } /** * {@inheritDoc} */ public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) { foreach ($this->getPropertyAnnotations($property) as $annot) { if ($annot instanceof $annotationName) { return $annot; } } return null; } } annotations/lib/Doctrine/Common/Annotations/TokenParser.php 0000644 00000014244 15120025740 0020112 0 ustar 00 <?php namespace Doctrine\Common\Annotations; use function array_merge; use function count; use function explode; use function strtolower; use function token_get_all; use const PHP_VERSION_ID; use const T_AS; use const T_COMMENT; use const T_DOC_COMMENT; use const T_NAME_FULLY_QUALIFIED; use const T_NAME_QUALIFIED; use const T_NAMESPACE; use const T_NS_SEPARATOR; use const T_STRING; use const T_USE; use const T_WHITESPACE; /** * Parses a file for namespaces/use/class declarations. */ class TokenParser { /** * The token list. * * @phpstan-var list<mixed[]> */ private $tokens; /** * The number of tokens. * * @var int */ private $numTokens; /** * The current array pointer. * * @var int */ private $pointer = 0; /** @param string $contents */ public function __construct($contents) { $this->tokens = token_get_all($contents); // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a // docblock. If the first thing in the file is a class without a doc block this would cause calls to // getDocBlock() on said class to return our long lost doc_comment. Argh. // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least // it's harmless to us. token_get_all("<?php\n/**\n *\n */"); $this->numTokens = count($this->tokens); } /** * Gets the next non whitespace and non comment token. * * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped. * If FALSE then only whitespace and normal comments are skipped. * * @return mixed[]|string|null The token if exists, null otherwise. */ public function next($docCommentIsComment = true) { for ($i = $this->pointer; $i < $this->numTokens; $i++) { $this->pointer++; if ( $this->tokens[$i][0] === T_WHITESPACE || $this->tokens[$i][0] === T_COMMENT || ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT) ) { continue; } return $this->tokens[$i]; } return null; } /** * Parses a single use statement. * * @return array<string, string> A list with all found class names for a use statement. */ public function parseUseStatement() { $groupRoot = ''; $class = ''; $alias = ''; $statements = []; $explicitAlias = false; while (($token = $this->next())) { if (! $explicitAlias && $token[0] === T_STRING) { $class .= $token[1]; $alias = $token[1]; } elseif ($explicitAlias && $token[0] === T_STRING) { $alias = $token[1]; } elseif ( PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) ) { $class .= $token[1]; $classSplit = explode('\\', $token[1]); $alias = $classSplit[count($classSplit) - 1]; } elseif ($token[0] === T_NS_SEPARATOR) { $class .= '\\'; $alias = ''; } elseif ($token[0] === T_AS) { $explicitAlias = true; $alias = ''; } elseif ($token === ',') { $statements[strtolower($alias)] = $groupRoot . $class; $class = ''; $alias = ''; $explicitAlias = false; } elseif ($token === ';') { $statements[strtolower($alias)] = $groupRoot . $class; break; } elseif ($token === '{') { $groupRoot = $class; $class = ''; } elseif ($token === '}') { continue; } else { break; } } return $statements; } /** * Gets all use statements. * * @param string $namespaceName The namespace name of the reflected class. * * @return array<string, string> A list with all found use statements. */ public function parseUseStatements($namespaceName) { $statements = []; while (($token = $this->next())) { if ($token[0] === T_USE) { $statements = array_merge($statements, $this->parseUseStatement()); continue; } if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) { continue; } // Get fresh array for new namespace. This is to prevent the parser to collect the use statements // for a previous namespace with the same name. This is the case if a namespace is defined twice // or if a namespace with the same name is commented out. $statements = []; } return $statements; } /** * Gets the namespace. * * @return string The found namespace. */ public function parseNamespace() { $name = ''; while ( ($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || ( PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) )) ) { $name .= $token[1]; } return $name; } /** * Gets the class name. * * @return string The found class name. */ public function parseClass() { // Namespaces and class names are tokenized the same: T_STRINGs // separated by T_NS_SEPARATOR so we can use one function to provide // both. return $this->parseNamespace(); } } annotations/LICENSE 0000644 00000002051 15120025740 0010100 0 ustar 00 Copyright (c) 2006-2013 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. annotations/README.md 0000644 00000002773 15120025740 0010365 0 ustar 00 ⚠️ PHP 8 introduced [attributes](https://www.php.net/manual/en/language.attributes.overview.php), which are a native replacement for annotations. As such, this library is considered feature complete, and should receive exclusively bugfixes and security fixes. # Doctrine Annotations [](https://github.com/doctrine/persistence/actions) [](https://www.versioneye.com/package/php--doctrine--annotations) [](https://www.versioneye.com/php/doctrine:annotations/references) [](https://packagist.org/packages/doctrine/annotations) [](https://packagist.org/packages/doctrine/annotations) Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)). ## Documentation See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html). ## Contributing When making a pull request, make sure your changes follow the [Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction). annotations/composer.json 0000644 00000004172 15120025740 0011623 0 ustar 00 { "name": "doctrine/annotations", "description": "Docblock Annotations Parser", "license": "MIT", "type": "library", "keywords": [ "annotations", "docblock", "parser" ], "authors": [ { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], "homepage": "https://www.doctrine-project.org/projects/annotations.html", "require": { "php": "^7.1 || ^8.0", "ext-tokenizer": "*", "doctrine/lexer": "^1 || ^2", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "~1.4.10 || ^1.8.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "symfony/cache": "^4.4 || ^5.4 || ^6", "vimeo/psalm": "^4.10" }, "suggest": { "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" }, "autoload": { "psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }, "autoload-dev": { "psr-4": { "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations", "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations" }, "files": [ "tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php", "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php" ] }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true } } annotations/psalm.xml 0000644 00000000730 15120025740 0010733 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="7" resolveFromConfigFile="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <projectFiles> <directory name="lib/Doctrine/Common/Annotations" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> </projectFiles> </psalm> dbal/bin/doctrine-dbal 0000644 00000000102 15120025740 0010635 0 ustar 00 #!/usr/bin/env php <?php require __DIR__ . '/doctrine-dbal.php'; dbal/bin/doctrine-dbal.php 0000644 00000002751 15120025740 0011437 0 ustar 00 <?php use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\DBAL\Tools\Console\ConsoleRunner; use Symfony\Component\Console\Helper\HelperSet; $files = [__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php']; $loader = null; $cwd = getcwd(); $directories = [$cwd, $cwd . DIRECTORY_SEPARATOR . 'config']; $configFile = null; foreach ($files as $file) { if (file_exists($file)) { $loader = require $file; break; } } if (! $loader) { throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?'); } foreach ($directories as $directory) { $configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php'; if (file_exists($configFile)) { break; } } if (! file_exists($configFile)) { ConsoleRunner::printCliConfigTemplate(); exit(1); } if (! is_readable($configFile)) { echo 'Configuration file [' . $configFile . '] does not have read permission.' . PHP_EOL; exit(1); } $commands = []; $helperSetOrConnectionProvider = require $configFile; if ( ! $helperSetOrConnectionProvider instanceof HelperSet && ! $helperSetOrConnectionProvider instanceof ConnectionProvider ) { foreach ($GLOBALS as $candidate) { if ($candidate instanceof HelperSet) { $helperSetOrConnectionProvider = $candidate; break; } } } ConsoleRunner::run($helperSetOrConnectionProvider, $commands); dbal/lib/Doctrine/DBAL/Abstraction/Result.php 0000644 00000002062 15120025740 0014721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Abstraction; use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Exception; use Traversable; /** * Abstraction-level result statement execution result. Provides additional methods on top * of the driver-level interface. * * @deprecated */ interface Result extends DriverResult { /** * Returns an iterator over the result set rows represented as numeric arrays. * * @return Traversable<int,array<int,mixed>> * * @throws Exception */ public function iterateNumeric(): Traversable; /** * Returns an iterator over the result set rows represented as associative arrays. * * @return Traversable<int,array<string,mixed>> * * @throws Exception */ public function iterateAssociative(): Traversable; /** * Returns an iterator over the values of the first column of the result set. * * @return Traversable<int,mixed> * * @throws Exception */ public function iterateColumn(): Traversable; } dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php 0000644 00000011545 15120025740 0015146 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use ArrayIterator; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\FetchMode; use InvalidArgumentException; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use function array_merge; use function array_values; use function count; use function reset; /** * @deprecated */ class ArrayStatement implements IteratorAggregate, ResultStatement, Result { /** @var mixed[] */ private $data; /** @var int */ private $columnCount = 0; /** @var int */ private $num = 0; /** @var int */ private $defaultFetchMode = FetchMode::MIXED; /** * @param mixed[] $data */ public function __construct(array $data) { $this->data = $data; if (! count($data)) { return; } $this->columnCount = count($data[0]); } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->free(); return true; } /** * {@inheritdoc} */ public function rowCount() { return count($this->data); } /** * {@inheritdoc} */ public function columnCount() { return $this->columnCount; } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { if ($arg2 !== null || $arg3 !== null) { throw new InvalidArgumentException('Caching layer does not support 2nd/3rd argument to setFetchMode()'); } $this->defaultFetchMode = $fetchMode; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { $data = $this->fetchAll(); return new ArrayIterator($data); } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { if (! isset($this->data[$this->num])) { return false; } $row = $this->data[$this->num++]; $fetchMode = $fetchMode ?: $this->defaultFetchMode; if ($fetchMode === FetchMode::ASSOCIATIVE) { return $row; } if ($fetchMode === FetchMode::NUMERIC) { return array_values($row); } if ($fetchMode === FetchMode::MIXED) { return array_merge($row, array_values($row)); } if ($fetchMode === FetchMode::COLUMN) { return reset($row); } throw new InvalidArgumentException('Invalid fetch-style given for fetching result.'); } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $rows = []; while ($row = $this->fetch($fetchMode)) { $rows[] = $row; } return $rows; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); // TODO: verify that return false is the correct behavior return $row[$columnIndex] ?? false; } /** * {@inheritdoc} */ public function fetchNumeric() { $row = $this->doFetch(); if ($row === false) { return false; } return array_values($row); } /** * {@inheritdoc} */ public function fetchAssociative() { return $this->doFetch(); } /** * {@inheritdoc} */ public function fetchOne() { $row = $this->doFetch(); if ($row === false) { return false; } return reset($row); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } public function free(): void { $this->data = []; } /** * @return mixed|false */ private function doFetch() { if (! isset($this->data[$this->num])) { return false; } return $this->data[$this->num++]; } } dbal/lib/Doctrine/DBAL/Cache/CacheException.php 0000644 00000000751 15120025740 0015062 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\DBAL\Exception; /** * @psalm-immutable */ class CacheException extends Exception { /** * @return CacheException */ public static function noCacheKey() { return new self('No cache key was set.'); } /** * @return CacheException */ public static function noResultDriverConfigured() { return new self('Trying to cache a query but no result driver is configured.'); } } dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php 0000644 00000006067 15120025740 0015560 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use Doctrine\Common\Cache\Cache; use Doctrine\DBAL\Types\Type; use function hash; use function serialize; use function sha1; /** * Query Cache Profile handles the data relevant for query caching. * * It is a value object, setter methods return NEW instances. */ class QueryCacheProfile { /** @var Cache|null */ private $resultCacheDriver; /** @var int */ private $lifetime = 0; /** @var string|null */ private $cacheKey; /** * @param int $lifetime * @param string|null $cacheKey */ public function __construct($lifetime = 0, $cacheKey = null, ?Cache $resultCache = null) { $this->lifetime = $lifetime; $this->cacheKey = $cacheKey; $this->resultCacheDriver = $resultCache; } /** * @return Cache|null */ public function getResultCacheDriver() { return $this->resultCacheDriver; } /** * @return int */ public function getLifetime() { return $this->lifetime; } /** * @return string * * @throws CacheException */ public function getCacheKey() { if ($this->cacheKey === null) { throw CacheException::noCacheKey(); } return $this->cacheKey; } /** * Generates the real cache key from query, params, types and connection parameters. * * @param string $sql * @param array<int, mixed>|array<string, mixed> $params * @param array<int, Type|int|string|null>|array<string, Type|int|string|null> $types * @param array<string, mixed> $connectionParams * * @return string[] */ public function generateCacheKeys($sql, $params, $types, array $connectionParams = []) { $realCacheKey = 'query=' . $sql . '¶ms=' . serialize($params) . '&types=' . serialize($types) . '&connectionParams=' . hash('sha256', serialize($connectionParams)); // should the key be automatically generated using the inputs or is the cache key set? if ($this->cacheKey === null) { $cacheKey = sha1($realCacheKey); } else { $cacheKey = $this->cacheKey; } return [$cacheKey, $realCacheKey]; } /** * @return QueryCacheProfile */ public function setResultCacheDriver(Cache $cache) { return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); } /** * @param string|null $cacheKey * * @return QueryCacheProfile */ public function setCacheKey($cacheKey) { return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCacheDriver); } /** * @param int $lifetime * * @return QueryCacheProfile */ public function setLifetime($lifetime) { return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCacheDriver); } } dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php 0000644 00000021323 15120025740 0016265 0 ustar 00 <?php namespace Doctrine\DBAL\Cache; use ArrayIterator; use Doctrine\Common\Cache\Cache; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\FetchMode; use InvalidArgumentException; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use function array_map; use function array_merge; use function array_values; use function assert; use function reset; /** * Cache statement for SQL results. * * A result is saved in multiple cache keys, there is the originally specified * cache key which is just pointing to result rows by key. The following things * have to be ensured: * * 1. lifetime of the original key has to be longer than that of all the individual rows keys * 2. if any one row key is missing the query has to be re-executed. * * Also you have to realize that the cache will load the whole result into memory at once to ensure 2. * This means that the memory usage for cached results might increase by using this feature. * * @deprecated */ class ResultCacheStatement implements IteratorAggregate, ResultStatement, Result { /** @var Cache */ private $resultCache; /** @var string */ private $cacheKey; /** @var string */ private $realKey; /** @var int */ private $lifetime; /** @var ResultStatement */ private $statement; /** @var array<int,array<string,mixed>>|null */ private $data; /** @var int */ private $defaultFetchMode = FetchMode::MIXED; /** * @param string $cacheKey * @param string $realKey * @param int $lifetime */ public function __construct(ResultStatement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime) { $this->statement = $stmt; $this->resultCache = $resultCache; $this->cacheKey = $cacheKey; $this->realKey = $realKey; $this->lifetime = $lifetime; } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->free(); return true; } /** * {@inheritdoc} */ public function columnCount() { return $this->statement->columnCount(); } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->defaultFetchMode = $fetchMode; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { $data = $this->fetchAll(); return new ArrayIterator($data); } /** * Be warned that you will need to call this method until no rows are * available for caching to happen. * * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { if ($this->data === null) { $this->data = []; } $row = $this->statement->fetch(FetchMode::ASSOCIATIVE); if ($row) { $this->data[] = $row; $fetchMode = $fetchMode ?: $this->defaultFetchMode; if ($fetchMode === FetchMode::ASSOCIATIVE) { return $row; } if ($fetchMode === FetchMode::NUMERIC) { return array_values($row); } if ($fetchMode === FetchMode::MIXED) { return array_merge($row, array_values($row)); } if ($fetchMode === FetchMode::COLUMN) { return reset($row); } throw new InvalidArgumentException('Invalid fetch-style given for caching result.'); } $this->saveToCache(); return false; } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE, $fetchArgument, $ctorArgs); $this->data = $data; $this->saveToCache(); if ($fetchMode === FetchMode::NUMERIC) { foreach ($data as $i => $row) { $data[$i] = array_values($row); } } elseif ($fetchMode === FetchMode::MIXED) { foreach ($data as $i => $row) { $data[$i] = array_merge($row, array_values($row)); } } elseif ($fetchMode === FetchMode::COLUMN) { foreach ($data as $i => $row) { $data[$i] = reset($row); } } return $data; } /** * Be warned that you will need to call this method until no rows are * available for caching to happen. * * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); // TODO: verify that return false is the correct behavior return $row[$columnIndex] ?? false; } /** * Be warned that you will need to call this method until no rows are * available for caching to happen. * * {@inheritdoc} */ public function fetchNumeric() { $row = $this->doFetch(); if ($row === false) { return false; } return array_values($row); } /** * Be warned that you will need to call this method until no rows are * available for caching to happen. * * {@inheritdoc} */ public function fetchAssociative() { return $this->doFetch(); } /** * Be warned that you will need to call this method until no rows are * available for caching to happen. * * {@inheritdoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { if ($this->statement instanceof Result) { $data = $this->statement->fetchAllAssociative(); } else { $data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE); } $this->data = $data; $this->saveToCache(); return array_map('array_values', $data); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { if ($this->statement instanceof Result) { $data = $this->statement->fetchAllAssociative(); } else { $data = $this->statement->fetchAll(FetchMode::ASSOCIATIVE); } $this->data = $data; $this->saveToCache(); return $data; } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } /** * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement * executed by the corresponding object. * * If the last SQL statement executed by the associated Statement object was a SELECT statement, * some databases may return the number of rows returned by that statement. However, * this behaviour is not guaranteed for all databases and should not be * relied on for portable applications. * * @return int|string The number of rows. */ public function rowCount() { assert($this->statement instanceof Statement); return $this->statement->rowCount(); } public function free(): void { $this->data = null; } /** * @return array<string,mixed>|false * * @throws Exception */ private function doFetch() { if ($this->data === null) { $this->data = []; } if ($this->statement instanceof Result) { $row = $this->statement->fetchAssociative(); } else { $row = $this->statement->fetch(FetchMode::ASSOCIATIVE); } if ($row !== false) { $this->data[] = $row; return $row; } $this->saveToCache(); return false; } private function saveToCache(): void { if ($this->data === null) { return; } $data = $this->resultCache->fetch($this->cacheKey); if (! $data) { $data = []; } $data[$this->realKey] = $this->data; $this->resultCache->save($this->cacheKey, $data, $this->lifetime); } } dbal/lib/Doctrine/DBAL/Connections/MasterSlaveConnection.php 0000644 00000005354 15120025740 0017731 0 ustar 00 <?php namespace Doctrine\DBAL\Connections; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; /** * @deprecated Use PrimaryReadReplicaConnection instead * * @psalm-import-type Params from DriverManager */ class MasterSlaveConnection extends PrimaryReadReplicaConnection { /** * Creates Primary Replica Connection. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params * @psalm-param Params $params * @phpstan-param array<string,mixed> $params * * @throws InvalidArgumentException */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { $this->deprecated(self::class, PrimaryReadReplicaConnection::class); if (isset($params['master'])) { $this->deprecated('Params key "master"', '"primary"'); $params['primary'] = $params['master']; } if (isset($params['slaves'])) { $this->deprecated('Params key "slaves"', '"replica"'); $params['replica'] = $params['slaves']; } if (isset($params['keepSlave'])) { $this->deprecated('Params key "keepSlave"', '"keepReplica"'); $params['keepReplica'] = $params['keepSlave']; } parent::__construct($params, $driver, $config, $eventManager); } /** * Checks if the connection is currently towards the primary or not. */ public function isConnectedToMaster(): bool { $this->deprecated('isConnectedtoMaster()', 'isConnectedToPrimary()'); return $this->isConnectedToPrimary(); } /** * @param string|null $connectionName * * @return bool */ public function connect($connectionName = null) { if ($connectionName === 'master') { $connectionName = 'primary'; $this->deprecated('connect("master")', 'ensureConnectedToPrimary()'); } if ($connectionName === 'slave') { $connectionName = 'replica'; $this->deprecated('connect("slave")', 'ensureConnectedToReplica()'); } return $this->performConnect($connectionName); } private function deprecated(string $thing, string $instead): void { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4054', '%s is deprecated since doctrine/dbal 2.11 and will be removed in 3.0, use %s instead.', $thing, $instead ); } } dbal/lib/Doctrine/DBAL/Connections/PrimaryReadReplicaConnection.php 0000644 00000030403 15120025740 0021213 0 ustar 00 <?php namespace Doctrine\DBAL\Connections; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use InvalidArgumentException; use function array_rand; use function assert; use function count; use function func_get_args; /** * Primary-Replica Connection * * Connection can be used with primary-replica setups. * * Important for the understanding of this connection should be how and when * it picks the replica or primary. * * 1. Replica if primary was never picked before and ONLY if 'getWrappedConnection' * or 'executeQuery' is used. * 2. Primary picked when 'exec', 'executeUpdate', 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint', * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit', 'query' or * 'prepare' is called. * 3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards. * 4. One replica connection is randomly picked ONCE during a request. * * ATTENTION: You can write to the replica with this connection if you execute a write query without * opening up a transaction. For example: * * $conn = DriverManager::getConnection(...); * $conn->executeQuery("DELETE FROM table"); * * Be aware that Connection#executeQuery is a method specifically for READ * operations only. * * Use Connection#executeStatement for any SQL statement that changes/updates * state in the database (UPDATE, INSERT, DELETE or DDL statements). * * This connection is limited to replica operations using the * Connection#executeQuery operation only, because it wouldn't be compatible * with the ORM or SchemaManager code otherwise. Both use all the other * operations in a context where writes could happen to a replica, which makes * this restricted approach necessary. * * You can manually connect to the primary at any time by calling: * * $conn->ensureConnectedToPrimary(); * * Instantiation through the DriverManager looks like: * * @psalm-import-type Params from DriverManager * @example * * $conn = DriverManager::getConnection(array( * 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection', * 'driver' => 'pdo_mysql', * 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), * 'replica' => array( * array('user' => 'replica1', 'password', 'host' => '', 'dbname' => ''), * array('user' => 'replica2', 'password', 'host' => '', 'dbname' => ''), * ) * )); * * You can also pass 'driverOptions' and any other documented option to each of this drivers * to pass additional information. */ class PrimaryReadReplicaConnection extends Connection { /** * Primary and Replica connection (one of the randomly picked replicas). * * @var DriverConnection[]|null[] */ protected $connections = ['primary' => null, 'replica' => null]; /** * You can keep the replica connection and then switch back to it * during the request if you know what you are doing. * * @var bool */ protected $keepReplica = false; /** * Creates Primary Replica Connection. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params * @psalm-param Params $params * @phpstan-param array<string,mixed> $params * * @throws InvalidArgumentException */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { if (! isset($params['replica'], $params['primary'])) { throw new InvalidArgumentException('primary or replica configuration missing'); } if (count($params['replica']) === 0) { throw new InvalidArgumentException('You have to configure at least one replica.'); } if (isset($params['driver'])) { $params['primary']['driver'] = $params['driver']; foreach ($params['replica'] as $replicaKey => $replica) { $params['replica'][$replicaKey]['driver'] = $params['driver']; } } $this->keepReplica = (bool) ($params['keepReplica'] ?? false); parent::__construct($params, $driver, $config, $eventManager); } /** * Checks if the connection is currently towards the primary or not. */ public function isConnectedToPrimary(): bool { return $this->_conn !== null && $this->_conn === $this->connections['primary']; } /** * @param string|null $connectionName * * @return bool */ public function connect($connectionName = null) { if ($connectionName !== null) { throw new InvalidArgumentException( 'Passing a connection name as first argument is not supported anymore.' . ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.' ); } return $this->performConnect(); } protected function performConnect(?string $connectionName = null): bool { $requestedConnectionChange = ($connectionName !== null); $connectionName = $connectionName ?: 'replica'; if ($connectionName !== 'replica' && $connectionName !== 'primary') { throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.'); } // If we have a connection open, and this is not an explicit connection // change request, then abort right here, because we are already done. // This prevents writes to the replica in case of "keepReplica" option enabled. if ($this->_conn !== null && ! $requestedConnectionChange) { return false; } $forcePrimaryAsReplica = false; if ($this->getTransactionNestingLevel() > 0) { $connectionName = 'primary'; $forcePrimaryAsReplica = true; } if (isset($this->connections[$connectionName])) { $this->_conn = $this->connections[$connectionName]; if ($forcePrimaryAsReplica && ! $this->keepReplica) { $this->connections['replica'] = $this->_conn; } return false; } if ($connectionName === 'primary') { $this->connections['primary'] = $this->_conn = $this->connectTo($connectionName); // Set replica connection to primary to avoid invalid reads if (! $this->keepReplica) { $this->connections['replica'] = $this->connections['primary']; } } else { $this->connections['replica'] = $this->_conn = $this->connectTo($connectionName); } if ($this->_eventManager->hasListeners(Events::postConnect)) { $eventArgs = new ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Connects to the primary node of the database cluster. * * All following statements after this will be executed against the primary node. */ public function ensureConnectedToPrimary(): bool { return $this->performConnect('primary'); } /** * Connects to a replica node of the database cluster. * * All following statements after this will be executed against the replica node, * unless the keepReplica option is set to false and a primary connection * was already opened. */ public function ensureConnectedToReplica(): bool { return $this->performConnect('replica'); } /** * Connects to a specific connection. * * @param string $connectionName * * @return DriverConnection */ protected function connectTo($connectionName) { $params = $this->getParams(); $driverOptions = $params['driverOptions'] ?? []; $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); $user = $connectionParams['user'] ?? null; $password = $connectionParams['password'] ?? null; return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); } /** * @param string $connectionName * @param mixed[] $params * * @return mixed */ protected function chooseConnectionConfiguration($connectionName, $params) { if ($connectionName === 'primary') { return $params['primary']; } $config = $params['replica'][array_rand($params['replica'])]; if (! isset($config['charset']) && isset($params['primary']['charset'])) { $config['charset'] = $params['primary']['charset']; } return $config; } /** * {@inheritDoc} * * @deprecated Use {@link executeStatement()} instead. */ public function executeUpdate($sql, array $params = [], array $types = []) { $this->ensureConnectedToPrimary(); return parent::executeUpdate($sql, $params, $types); } /** * {@inheritDoc} */ public function executeStatement($sql, array $params = [], array $types = []) { $this->ensureConnectedToPrimary(); return parent::executeStatement($sql, $params, $types); } /** * {@inheritDoc} */ public function beginTransaction() { $this->ensureConnectedToPrimary(); return parent::beginTransaction(); } /** * {@inheritDoc} */ public function commit() { $this->ensureConnectedToPrimary(); return parent::commit(); } /** * {@inheritDoc} */ public function rollBack() { $this->ensureConnectedToPrimary(); return parent::rollBack(); } /** * {@inheritDoc} */ public function delete($table, array $criteria, array $types = []) { $this->ensureConnectedToPrimary(); return parent::delete($table, $criteria, $types); } /** * {@inheritDoc} */ public function close() { unset($this->connections['primary'], $this->connections['replica']); parent::close(); $this->_conn = null; $this->connections = ['primary' => null, 'replica' => null]; } /** * {@inheritDoc} */ public function update($table, array $data, array $criteria, array $types = []) { $this->ensureConnectedToPrimary(); return parent::update($table, $data, $criteria, $types); } /** * {@inheritDoc} */ public function insert($table, array $data, array $types = []) { $this->ensureConnectedToPrimary(); return parent::insert($table, $data, $types); } /** * {@inheritDoc} */ public function exec($sql) { $this->ensureConnectedToPrimary(); return parent::exec($sql); } /** * {@inheritDoc} */ public function createSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::createSavepoint($savepoint); } /** * {@inheritDoc} */ public function releaseSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::releaseSavepoint($savepoint); } /** * {@inheritDoc} */ public function rollbackSavepoint($savepoint) { $this->ensureConnectedToPrimary(); parent::rollbackSavepoint($savepoint); } /** * {@inheritDoc} */ public function query() { $this->ensureConnectedToPrimary(); assert($this->_conn instanceof DriverConnection); $args = func_get_args(); $logger = $this->getConfiguration()->getSQLLogger(); if ($logger) { $logger->startQuery($args[0]); } $statement = $this->_conn->query(...$args); $statement->setFetchMode($this->defaultFetchMode); if ($logger) { $logger->stopQuery(); } return $statement; } /** * {@inheritDoc} */ public function prepare($sql) { $this->ensureConnectedToPrimary(); return parent::prepare($sql); } } dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver/EasyConnectString.php 0000644 00000005344 15120025740 0022102 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractOracleDriver; use function implode; use function is_array; use function sprintf; /** * Represents an Oracle Easy Connect string * * @link https://docs.oracle.com/database/121/NETAG/naming.htm */ final class EasyConnectString { /** @var string */ private $string; private function __construct(string $string) { $this->string = $string; } public function __toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param mixed[] $params */ public static function fromArray(array $params): self { return new self(self::renderParams($params)); } /** * Creates the object from the given DBAL connection parameters. * * @param mixed[] $params */ public static function fromConnectionParameters(array $params): self { if (! empty($params['connectstring'])) { return new self($params['connectstring']); } if (empty($params['host'])) { return new self($params['dbname'] ?? ''); } $connectData = []; if (isset($params['servicename']) || isset($params['dbname'])) { $serviceKey = 'SID'; if (! empty($params['service'])) { $serviceKey = 'SERVICE_NAME'; } $serviceName = $params['servicename'] ?? $params['dbname']; $connectData[$serviceKey] = $serviceName; } if (! empty($params['instancename'])) { $connectData['INSTANCE_NAME'] = $params['instancename']; } if (! empty($params['pooled'])) { $connectData['SERVER'] = 'POOLED'; } return self::fromArray([ 'DESCRIPTION' => [ 'ADDRESS' => [ 'PROTOCOL' => 'TCP', 'HOST' => $params['host'], 'PORT' => $params['port'] ?? 1521, ], 'CONNECT_DATA' => $connectData, ], ]); } /** * @param mixed[] $params */ private static function renderParams(array $params): string { $chunks = []; foreach ($params as $key => $value) { $string = self::renderValue($value); if ($string === '') { continue; } $chunks[] = sprintf('(%s=%s)', $key, $string); } return implode('', $chunks); } /** * @param mixed $value */ private static function renderValue($value): string { if (is_array($value)) { return self::renderParams($value); } return (string) $value; } } dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php 0000644 00000000575 15120025740 0024226 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception; use Doctrine\DBAL\Driver\AbstractDriverException; /** * @internal * * @psalm-immutable */ final class PortWithoutHost extends AbstractDriverException { public static function new(): self { return new self('Connection port specified without the host'); } } dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php 0000644 00000000714 15120025740 0017502 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; use Doctrine\DBAL\Driver\PDOConnection; use Doctrine\DBAL\ParameterType; /** * @deprecated */ class Connection extends PDOConnection { /** * {@inheritdoc} */ public function quote($value, $type = ParameterType::STRING) { if ($type === ParameterType::BOOLEAN) { return $value ? 'true' : 'false'; } return parent::quote($value, $type); } } dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php 0000644 00000002363 15120025740 0016640 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; use Doctrine\DBAL\Platforms\DrizzlePlatform; use Doctrine\DBAL\Schema\DrizzleSchemaManager; /** * Drizzle driver using PDO MySql. * * @deprecated */ class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { return new Connection( $this->constructPdoDsn($params), $username, $password, $driverOptions ); } /** * {@inheritdoc} */ public function createDatabasePlatformForVersion($version) { return $this->getDatabasePlatform(); } /** * {@inheritdoc} * * @return DrizzlePlatform */ public function getDatabasePlatform() { return new DrizzlePlatform(); } /** * {@inheritdoc} * * @return DrizzleSchemaManager */ public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new DrizzleSchemaManager($conn); } /** * {@inheritdoc} * * @deprecated */ public function getName() { return 'drizzle_pdo_mysql'; } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php 0000644 00000001070 15120025740 0022170 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\IBMDB2\DB2Exception; /** * @internal * * @psalm-immutable */ final class CannotCopyStreamToStream extends DB2Exception { /** * @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not copy source stream to temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php 0000644 00000001052 15120025740 0022331 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\IBMDB2\DB2Exception; /** * @internal * * @psalm-immutable */ final class CannotCreateTemporaryFile extends DB2Exception { /** * @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not create temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotWriteToTemporaryFile.php 0000644 00000001064 15120025740 0022526 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\IBMDB2\DB2Exception; /** * @internal * * @psalm-immutable */ final class CannotWriteToTemporaryFile extends DB2Exception { /** * @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { $message = 'Could not write string to temporary file'; if ($error !== null) { $message .= ': ' . $error['message']; } return new self($message); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php 0000644 00000000753 15120025740 0020400 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionError extends AbstractException { /** * @param resource $connection */ public static function new($connection): self { return new self(db2_conn_errormsg($connection), db2_conn_error($connection)); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionFailed.php 0000644 00000000630 15120025740 0020465 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_conn_error; use function db2_conn_errormsg; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends AbstractException { public static function new(): self { return new self(db2_conn_errormsg(), db2_conn_error()); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/PrepareFailed.php 0000644 00000000757 15120025740 0017776 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; /** * @internal * * @psalm-immutable */ final class PrepareFailed extends AbstractException { /** * @psalm-param array{message: string}|null $error */ public static function new(?array $error): self { if ($error === null) { return new self('Unknown error'); } return new self($error['message']); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/StatementError.php 0000644 00000000746 15120025740 0020247 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2\Exception; use Doctrine\DBAL\Driver\AbstractException; use function db2_stmt_error; use function db2_stmt_errormsg; /** * @internal * * @psalm-immutable */ final class StatementError extends AbstractException { /** * @param resource $statement */ public static function new($statement): self { return new self(db2_stmt_errormsg($statement), db2_stmt_error($statement)); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php 0000644 00000000140 15120025740 0015416 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; final class Connection extends DB2Connection { } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php 0000644 00000011656 15120025740 0015724 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError; use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use stdClass; use function assert; use function db2_autocommit; use function db2_commit; use function db2_conn_error; use function db2_conn_errormsg; use function db2_connect; use function db2_escape_string; use function db2_exec; use function db2_last_insert_id; use function db2_num_rows; use function db2_pconnect; use function db2_prepare; use function db2_rollback; use function db2_server_info; use function error_get_last; use function func_get_args; use function is_bool; use const DB2_AUTOCOMMIT_OFF; use const DB2_AUTOCOMMIT_ON; /** * @deprecated Use {@link Connection} instead */ class DB2Connection implements ConnectionInterface, ServerInfoAwareConnection { /** @var resource */ private $conn; /** * @internal The connection can be only instantiated by its driver. * * @param mixed[] $params * @param string $username * @param string $password * @param mixed[] $driverOptions * * @throws DB2Exception */ public function __construct(array $params, $username, $password, $driverOptions = []) { $isPersistent = (isset($params['persistent']) && $params['persistent'] === true); if ($isPersistent) { $conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); } else { $conn = db2_connect($params['dbname'], $username, $password, $driverOptions); } if ($conn === false) { throw ConnectionFailed::new(); } $this->conn = $conn; } /** * {@inheritdoc} */ public function getServerVersion() { $serverInfo = db2_server_info($this->conn); assert($serverInfo instanceof stdClass); return $serverInfo->DBMS_VER; } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return false; } /** * {@inheritdoc} */ public function prepare($sql) { $stmt = @db2_prepare($this->conn, $sql); if ($stmt === false) { throw PrepareFailed::new(error_get_last()); } return new Statement($stmt); } /** * {@inheritdoc} */ public function query() { $args = func_get_args(); $sql = $args[0]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } /** * {@inheritdoc} */ public function quote($value, $type = ParameterType::STRING) { $value = db2_escape_string($value); if ($type === ParameterType::INTEGER) { return $value; } return "'" . $value . "'"; } /** * {@inheritdoc} */ public function exec($sql) { $stmt = @db2_exec($this->conn, $sql); if ($stmt === false) { throw ConnectionError::new($this->conn); } return db2_num_rows($stmt); } /** * {@inheritdoc} */ public function lastInsertId($name = null) { return db2_last_insert_id($this->conn); } /** * {@inheritdoc} */ public function beginTransaction() { $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_OFF); assert(is_bool($result)); return $result; } /** * {@inheritdoc} */ public function commit() { if (! db2_commit($this->conn)) { throw ConnectionError::new($this->conn); } $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); assert(is_bool($result)); return $result; } /** * {@inheritdoc} */ public function rollBack() { if (! db2_rollback($this->conn)) { throw ConnectionError::new($this->conn); } $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); assert(is_bool($result)); return $result; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { return db2_conn_error($this->conn); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return [ 0 => db2_conn_errormsg($this->conn), 1 => $this->errorCode(), ]; } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php 0000644 00000002023 15120025740 0015044 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\AbstractDB2Driver; use Doctrine\Deprecations\Deprecation; /** * IBM DB2 Driver. * * @deprecated Use {@link Driver} instead */ class DB2Driver extends AbstractDB2Driver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { $params['user'] = $username; $params['password'] = $password; $params['dbname'] = DataSourceName::fromConnectionParameters($params)->toString(); return new Connection( $params, (string) $username, (string) $password, $driverOptions ); } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'ibm_db2'; } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php 0000644 00000000373 15120025740 0015555 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\AbstractDriverException; /** * @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead * * @psalm-immutable */ class DB2Exception extends AbstractDriverException { } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php 0000644 00000034711 15120025740 0015566 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile; use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotWriteToTemporaryFile; use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use IteratorAggregate; use PDO; use ReflectionClass; use ReflectionObject; use ReflectionProperty; use ReturnTypeWillChange; use stdClass; use function array_change_key_case; use function assert; use function db2_bind_param; use function db2_execute; use function db2_fetch_array; use function db2_fetch_assoc; use function db2_fetch_both; use function db2_fetch_object; use function db2_free_result; use function db2_num_fields; use function db2_num_rows; use function db2_stmt_error; use function db2_stmt_errormsg; use function error_get_last; use function fclose; use function func_get_args; use function func_num_args; use function fwrite; use function gettype; use function is_int; use function is_object; use function is_resource; use function is_string; use function ksort; use function sprintf; use function stream_copy_to_stream; use function stream_get_meta_data; use function strtolower; use function tmpfile; use const CASE_LOWER; use const DB2_BINARY; use const DB2_CHAR; use const DB2_LONG; use const DB2_PARAM_FILE; use const DB2_PARAM_IN; /** * @deprecated Use {@link Statement} instead */ class DB2Statement implements IteratorAggregate, StatementInterface, Result { /** @var resource */ private $stmt; /** @var mixed[] */ private $bindParam = []; /** * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement * and the temporary file handle bound to the underlying statement * * @var mixed[][] */ private $lobs = []; /** @var string Name of the default class to instantiate when fetching class instances. */ private $defaultFetchClass = '\stdClass'; /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ private $defaultFetchClassCtorArgs = []; /** @var int */ private $defaultFetchMode = FetchMode::MIXED; /** * Indicates whether the statement is in the state when fetching results is possible * * @var bool */ private $result = false; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $stmt */ public function __construct($stmt) { $this->stmt = $stmt; } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { assert(is_int($param)); return $this->bindParam($param, $value, $type); } /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { assert(is_int($param)); switch ($type) { case ParameterType::INTEGER: $this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG); break; case ParameterType::LARGE_OBJECT: if (isset($this->lobs[$param])) { [, $handle] = $this->lobs[$param]; fclose($handle); } $handle = $this->createTemporaryFile(); $path = stream_get_meta_data($handle)['uri']; $this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY); $this->lobs[$param] = [&$variable, $handle]; break; default: $this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR); break; } return true; } /** * @param int $position Parameter position * @param mixed $variable * * @throws DB2Exception */ private function bind($position, &$variable, int $parameterType, int $dataType): void { $this->bindParam[$position] =& $variable; if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) { throw StatementError::new($this->stmt); } } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->bindParam = []; if (! db2_free_result($this->stmt)) { return false; } $this->result = false; return true; } /** * {@inheritdoc} */ public function columnCount() { return db2_num_fields($this->stmt) ?: 0; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { return db2_stmt_error(); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return [ db2_stmt_errormsg(), db2_stmt_error(), ]; } /** * {@inheritdoc} */ public function execute($params = null) { if ($params === null) { ksort($this->bindParam); $params = []; foreach ($this->bindParam as $column => $value) { $params[] = $value; } } foreach ($this->lobs as [$source, $target]) { if (is_resource($source)) { $this->copyStreamToStream($source, $target); continue; } $this->writeStringToStream($source, $target); } $retval = db2_execute($this->stmt, $params); foreach ($this->lobs as [, $handle]) { fclose($handle); } $this->lobs = []; if ($retval === false) { throw StatementError::new($this->stmt); } $this->result = true; return $retval; } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->defaultFetchMode = $fetchMode; $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if (! $this->result) { return false; } $fetchMode = $fetchMode ?: $this->defaultFetchMode; switch ($fetchMode) { case FetchMode::COLUMN: return $this->fetchColumn(); case FetchMode::MIXED: return db2_fetch_both($this->stmt); case FetchMode::ASSOCIATIVE: return db2_fetch_assoc($this->stmt); case FetchMode::CUSTOM_OBJECT: $className = $this->defaultFetchClass; $ctorArgs = $this->defaultFetchClassCtorArgs; if (func_num_args() >= 2) { $args = func_get_args(); $className = $args[1]; $ctorArgs = $args[2] ?? []; } $result = db2_fetch_object($this->stmt); if ($result instanceof stdClass) { $result = $this->castObject($result, $className, $ctorArgs); } return $result; case FetchMode::NUMERIC: return db2_fetch_array($this->stmt); case FetchMode::STANDARD_OBJECT: return db2_fetch_object($this->stmt); default: throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.'); } } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $rows = []; switch ($fetchMode) { case FetchMode::CUSTOM_OBJECT: while (($row = $this->fetch(...func_get_args())) !== false) { $rows[] = $row; } break; case FetchMode::COLUMN: while (($row = $this->fetchColumn()) !== false) { $rows[] = $row; } break; default: while (($row = $this->fetch($fetchMode)) !== false) { $rows[] = $row; } } return $rows; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * {@inheritDoc} */ public function fetchNumeric() { if (! $this->result) { return false; } return db2_fetch_array($this->stmt); } /** * {@inheritdoc} */ public function fetchAssociative() { // do not try fetching from the statement if it's not expected to contain the result // in order to prevent exceptional situation if (! $this->result) { return false; } return db2_fetch_assoc($this->stmt); } /** * {@inheritdoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } /** * {@inheritdoc} */ public function rowCount() { return @db2_num_rows($this->stmt) ? : 0; } public function free(): void { $this->bindParam = []; db2_free_result($this->stmt); $this->result = false; } /** * Casts a stdClass object to the given class name mapping its' properties. * * @param stdClass $sourceObject Object to cast from. * @param class-string|object $destinationClass Name of the class or class instance to cast to. * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. * * @return object * * @throws DB2Exception */ private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) { if (! is_string($destinationClass)) { if (! is_object($destinationClass)) { throw new DB2Exception(sprintf( 'Destination class has to be of type string or object, %s given.', gettype($destinationClass) )); } } else { $destinationClass = new ReflectionClass($destinationClass); $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); } $sourceReflection = new ReflectionObject($sourceObject); $destinationClassReflection = new ReflectionObject($destinationClass); /** @var ReflectionProperty[] $destinationProperties */ $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER); foreach ($sourceReflection->getProperties() as $sourceProperty) { $sourceProperty->setAccessible(true); $name = $sourceProperty->getName(); $value = $sourceProperty->getValue($sourceObject); // Try to find a case-matching property. if ($destinationClassReflection->hasProperty($name)) { $destinationProperty = $destinationClassReflection->getProperty($name); $destinationProperty->setAccessible(true); $destinationProperty->setValue($destinationClass, $value); continue; } $name = strtolower($name); // Try to find a property without matching case. // Fallback for the driver returning either all uppercase or all lowercase column names. if (isset($destinationProperties[$name])) { $destinationProperty = $destinationProperties[$name]; $destinationProperty->setAccessible(true); $destinationProperty->setValue($destinationClass, $value); continue; } $destinationClass->$name = $value; } return $destinationClass; } /** * @return resource * * @throws DB2Exception */ private function createTemporaryFile() { $handle = @tmpfile(); if ($handle === false) { throw CannotCreateTemporaryFile::new(error_get_last()); } return $handle; } /** * @param resource $source * @param resource $target * * @throws DB2Exception */ private function copyStreamToStream($source, $target): void { if (@stream_copy_to_stream($source, $target) === false) { throw CannotCopyStreamToStream::new(error_get_last()); } } /** * @param resource $target * * @throws DB2Exception */ private function writeStringToStream(string $string, $target): void { if (@fwrite($target, $string) === false) { throw CannotWriteToTemporaryFile::new(error_get_last()); } } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DataSourceName.php 0000644 00000003246 15120025740 0016164 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; use function implode; use function sprintf; use function strpos; /** * IBM DB2 DSN */ final class DataSourceName { /** @var string */ private $string; private function __construct(string $string) { $this->string = $string; } public function toString(): string { return $this->string; } /** * Creates the object from an array representation * * @param array<string,mixed> $params */ public static function fromArray(array $params): self { $chunks = []; foreach ($params as $key => $value) { $chunks[] = sprintf('%s=%s', $key, $value); } return new self(implode(';', $chunks)); } /** * Creates the object from the given DBAL connection parameters. * * @param array<string,mixed> $params */ public static function fromConnectionParameters(array $params): self { if (isset($params['dbname']) && strpos($params['dbname'], '=') !== false) { return new self($params['dbname']); } $dsnParams = []; foreach ( [ 'host' => 'HOSTNAME', 'port' => 'PORT', 'protocol' => 'PROTOCOL', 'dbname' => 'DATABASE', 'user' => 'UID', 'password' => 'PWD', ] as $dbalParam => $dsnParam ) { if (! isset($params[$dbalParam])) { continue; } $dsnParams[$dsnParam] = $params[$dbalParam]; } return self::fromArray($dsnParams); } } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Driver.php 0000644 00000000162 15120025740 0014556 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\IBMDB2; final class Driver extends DB2Driver { } dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Statement.php 0000644 00000000136 15120025740 0015270 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\IBMDB2; final class Statement extends DB2Statement { } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php 0000644 00000001372 15120025740 0020755 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class ConnectionError extends MysqliException { public static function new(mysqli $connection): self { return new self($connection->error, $connection->sqlstate, $connection->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionFailed.php 0000644 00000001516 15120025740 0021050 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use mysqli; use mysqli_sql_exception; use ReflectionProperty; use function assert; /** * @internal * * @psalm-immutable */ final class ConnectionFailed extends MysqliException { public static function new(mysqli $connection): self { $error = $connection->connect_error; assert($error !== null); return new self($error, 'HY000', $connection->connect_errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php 0000644 00000000662 15120025740 0022646 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use function sprintf; /** * @internal * * @psalm-immutable */ final class FailedReadingStreamOffset extends MysqliException { public static function new(int $offset): self { return new self(sprintf('Failed reading the stream resource for parameter offset %d.', $offset)); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/InvalidOption.php 0000644 00000001002 15120025740 0020411 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use function sprintf; /** * @internal * * @psalm-immutable */ final class InvalidOption extends MysqliException { /** * @param mixed $option * @param mixed $value */ public static function fromOption($option, $value): self { return new self( sprintf('Failed to set option %d with value "%s"', $option, $value) ); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php 0000644 00000001377 15120025740 0020627 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use mysqli_sql_exception; use mysqli_stmt; use ReflectionProperty; /** * @internal * * @psalm-immutable */ final class StatementError extends MysqliException { public static function new(mysqli_stmt $statement): self { return new self($statement->error, $statement->sqlstate, $statement->errno); } public static function upcast(mysqli_sql_exception $exception): self { $p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate'); $p->setAccessible(true); return new self($exception->getMessage(), $p->getValue($exception), $exception->getCode(), $exception); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php 0000644 00000000642 15120025740 0020144 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\Mysqli\Exception; use Doctrine\DBAL\Driver\Mysqli\MysqliException; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownType extends MysqliException { /** * @param mixed $type */ public static function new($type): self { return new self(sprintf('Unknown type, %d given.', $type)); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Connection.php 0000644 00000000143 15120025740 0016000 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; final class Connection extends MysqliConnection { } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php 0000644 00000001540 15120025740 0015136 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; class Driver extends AbstractMySQLDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { return new Connection($params, (string) $username, (string) $password, $driverOptions); } catch (MysqliException $e) { throw Exception::driverException($this, $e); } } /** * {@inheritdoc} */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'mysqli'; } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php 0000644 00000020770 15120025740 0017207 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed; use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption; use Doctrine\DBAL\Driver\PingableConnection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use mysqli; use mysqli_sql_exception; use function assert; use function floor; use function func_get_args; use function in_array; use function ini_get; use function mysqli_errno; use function mysqli_error; use function mysqli_init; use function mysqli_options; use function sprintf; use function stripos; use const MYSQLI_INIT_COMMAND; use const MYSQLI_OPT_CONNECT_TIMEOUT; use const MYSQLI_OPT_LOCAL_INFILE; use const MYSQLI_OPT_READ_TIMEOUT; use const MYSQLI_READ_DEFAULT_FILE; use const MYSQLI_READ_DEFAULT_GROUP; use const MYSQLI_SERVER_PUBLIC_KEY; /** * @deprecated Use {@link Connection} instead */ class MysqliConnection implements ConnectionInterface, PingableConnection, ServerInfoAwareConnection { /** * Name of the option to set connection flags */ public const OPTION_FLAGS = 'flags'; /** @var mysqli */ private $conn; /** * @internal The connection can be only instantiated by its driver. * * @param mixed[] $params * @param string $username * @param string $password * @param mixed[] $driverOptions * * @throws MysqliException */ public function __construct(array $params, $username, $password, array $driverOptions = []) { $port = $params['port'] ?? ini_get('mysqli.default_port'); // Fallback to default MySQL port if not given. if (! $port) { $port = 3306; } $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket'); $dbname = $params['dbname'] ?? null; $flags = $driverOptions[static::OPTION_FLAGS] ?? 0; $conn = mysqli_init(); assert($conn !== false); $this->conn = $conn; $this->setSecureConnection($params); $this->setDriverOptions($driverOptions); try { $success = @$this->conn ->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags); } catch (mysqli_sql_exception $e) { throw ConnectionFailed::upcast($e); } if (! $success) { throw ConnectionFailed::new($this->conn); } if (! isset($params['charset'])) { return; } $this->conn->set_charset($params['charset']); } /** * Retrieves mysqli native resource handle. * * Could be used if part of your application is not using DBAL. * * @return mysqli */ public function getWrappedResourceHandle() { return $this->conn; } /** * {@inheritdoc} * * The server version detection includes a special case for MariaDB * to support '5.5.5-' prefixed versions introduced in Maria 10+ * * @link https://jira.mariadb.org/browse/MDEV-4088 */ public function getServerVersion() { $serverInfos = $this->conn->get_server_info(); if (stripos($serverInfos, 'mariadb') !== false) { return $serverInfos; } $majorVersion = floor($this->conn->server_version / 10000); $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100); $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100); return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return false; } /** * {@inheritdoc} */ public function prepare($sql) { return new Statement($this->conn, $sql); } /** * {@inheritdoc} */ public function query() { $args = func_get_args(); $sql = $args[0]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } /** * {@inheritdoc} */ public function quote($value, $type = ParameterType::STRING) { return "'" . $this->conn->escape_string($value) . "'"; } /** * {@inheritdoc} */ public function exec($sql) { try { $result = $this->conn->query($sql); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($result === false) { throw ConnectionError::new($this->conn); } return $this->conn->affected_rows; } /** * {@inheritdoc} */ public function lastInsertId($name = null) { return $this->conn->insert_id; } /** * {@inheritdoc} */ public function beginTransaction() { $this->conn->query('START TRANSACTION'); return true; } /** * {@inheritdoc} */ public function commit() { try { return $this->conn->commit(); } catch (mysqli_sql_exception $e) { return false; } } /** * {@inheritdoc}non-PHPdoc) */ public function rollBack() { try { return $this->conn->rollback(); } catch (mysqli_sql_exception $e) { return false; } } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. * * @return int */ public function errorCode() { return $this->conn->errno; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. * * @return string */ public function errorInfo() { return $this->conn->error; } /** * Apply the driver options to the connection. * * @param mixed[] $driverOptions * * @throws MysqliException When one of of the options is not supported. * @throws MysqliException When applying doesn't work - e.g. due to incorrect value. */ private function setDriverOptions(array $driverOptions = []): void { $supportedDriverOptions = [ MYSQLI_OPT_CONNECT_TIMEOUT, MYSQLI_OPT_LOCAL_INFILE, MYSQLI_OPT_READ_TIMEOUT, MYSQLI_INIT_COMMAND, MYSQLI_READ_DEFAULT_FILE, MYSQLI_READ_DEFAULT_GROUP, MYSQLI_SERVER_PUBLIC_KEY, ]; $exceptionMsg = "%s option '%s' with value '%s'"; foreach ($driverOptions as $option => $value) { if ($option === static::OPTION_FLAGS) { continue; } if (! in_array($option, $supportedDriverOptions, true)) { throw InvalidOption::fromOption($option, $value); } if (@mysqli_options($this->conn, $option, $value)) { continue; } $msg = sprintf($exceptionMsg, 'Failed to set', $option, $value); $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn)); throw new MysqliException( $msg, $this->conn->sqlstate, $this->conn->errno ); } } /** * Pings the server and re-connects when `mysqli.reconnect = 1` * * @deprecated * * @return bool */ public function ping() { return $this->conn->ping(); } /** * Establish a secure connection * * @param array<string,string> $params * * @throws MysqliException */ private function setSecureConnection(array $params): void { if ( ! isset($params['ssl_key']) && ! isset($params['ssl_cert']) && ! isset($params['ssl_ca']) && ! isset($params['ssl_capath']) && ! isset($params['ssl_cipher']) ) { return; } $this->conn->ssl_set( $params['ssl_key'] ?? '', $params['ssl_cert'] ?? '', $params['ssl_ca'] ?? '', $params['ssl_capath'] ?? '', $params['ssl_cipher'] ?? '' ); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php 0000644 00000000376 15120025740 0017046 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\AbstractDriverException; /** * @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead * * @psalm-immutable */ class MysqliException extends AbstractDriverException { } dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php 0000644 00000035346 15120025740 0017061 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError; use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset; use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError; use Doctrine\DBAL\Driver\Mysqli\Exception\UnknownType; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use IteratorAggregate; use mysqli; use mysqli_sql_exception; use mysqli_stmt; use PDO; use ReturnTypeWillChange; use function array_combine; use function array_fill; use function assert; use function count; use function feof; use function fread; use function get_resource_type; use function is_array; use function is_int; use function is_resource; use function sprintf; use function str_repeat; /** * @deprecated Use {@link Statement} instead */ class MysqliStatement implements IteratorAggregate, StatementInterface, Result { /** @var string[] */ protected static $_paramTypeMap = [ ParameterType::ASCII => 's', ParameterType::STRING => 's', ParameterType::BINARY => 's', ParameterType::BOOLEAN => 'i', ParameterType::NULL => 's', ParameterType::INTEGER => 'i', ParameterType::LARGE_OBJECT => 'b', ]; /** @var mysqli */ protected $_conn; /** @var mysqli_stmt */ protected $_stmt; /** @var string[]|false|null */ protected $_columnNames; /** @var mixed[] */ protected $_rowBindedValues = []; /** @var mixed[] */ protected $_bindedValues; /** @var string */ protected $types; /** * Contains ref values for bindValue(). * * @var mixed[] */ protected $_values = []; /** @var int */ protected $_defaultFetchMode = FetchMode::MIXED; /** * Indicates whether the statement is in the state when fetching results is possible * * @var bool */ private $result = false; /** * @internal The statement can be only instantiated by its driver connection. * * @param string $prepareString * * @throws MysqliException */ public function __construct(mysqli $conn, $prepareString) { $this->_conn = $conn; try { $stmt = $conn->prepare($prepareString); } catch (mysqli_sql_exception $e) { throw ConnectionError::upcast($e); } if ($stmt === false) { throw ConnectionError::new($this->_conn); } $this->_stmt = $stmt; $paramCount = $this->_stmt->param_count; $this->types = str_repeat('s', $paramCount); $this->_bindedValues = array_fill(1, $paramCount, null); } /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { assert(is_int($param)); if (! isset(self::$_paramTypeMap[$type])) { throw UnknownType::new($type); } $this->_bindedValues[$param] =& $variable; $this->types[$param - 1] = self::$_paramTypeMap[$type]; return true; } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { assert(is_int($param)); if (! isset(self::$_paramTypeMap[$type])) { throw UnknownType::new($type); } $this->_values[$param] = $value; $this->_bindedValues[$param] =& $this->_values[$param]; $this->types[$param - 1] = self::$_paramTypeMap[$type]; return true; } /** * {@inheritdoc} */ public function execute($params = null) { if ($params !== null && count($params) > 0) { if (! $this->bindUntypedValues($params)) { throw StatementError::new($this->_stmt); } } elseif (count($this->_bindedValues) > 0) { $this->bindTypedParameters(); } try { $result = $this->_stmt->execute(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if (! $result) { throw StatementError::new($this->_stmt); } if ($this->_columnNames === null) { $meta = $this->_stmt->result_metadata(); if ($meta !== false) { $fields = $meta->fetch_fields(); assert(is_array($fields)); $columnNames = []; foreach ($fields as $col) { $columnNames[] = $col->name; } $meta->free(); $this->_columnNames = $columnNames; } else { $this->_columnNames = false; } } if ($this->_columnNames !== false) { // Store result of every execution which has it. Otherwise it will be impossible // to execute a new statement in case if the previous one has non-fetched rows // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html $this->_stmt->store_result(); // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql, // it will have to allocate as much memory as it may be needed for the given column type // (e.g. for a LONGBLOB column it's 4 gigabytes) // @link https://bugs.php.net/bug.php?id=51386#1270673122 // // Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been // previously called on the statement, the values are unbound making the statement unusable. // // It's also important that row values are bound after _each_ call to store_result(). Otherwise, // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated // to the length of the ones fetched during the previous execution. $this->_rowBindedValues = array_fill(0, count($this->_columnNames), null); $refs = []; foreach ($this->_rowBindedValues as $key => &$value) { $refs[$key] =& $value; } if (! $this->_stmt->bind_result(...$refs)) { throw StatementError::new($this->_stmt); } } $this->result = true; return true; } /** * Binds parameters with known types previously bound to the statement */ private function bindTypedParameters(): void { $streams = $values = []; $types = $this->types; foreach ($this->_bindedValues as $parameter => $value) { assert(is_int($parameter)); if (! isset($types[$parameter - 1])) { $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; } if ($types[$parameter - 1] === static::$_paramTypeMap[ParameterType::LARGE_OBJECT]) { if (is_resource($value)) { if (get_resource_type($value) !== 'stream') { throw new InvalidArgumentException( 'Resources passed with the LARGE_OBJECT parameter type must be stream resources.' ); } $streams[$parameter] = $value; $values[$parameter] = null; continue; } $types[$parameter - 1] = static::$_paramTypeMap[ParameterType::STRING]; } $values[$parameter] = $value; } if (! $this->_stmt->bind_param($types, ...$values)) { throw StatementError::new($this->_stmt); } $this->sendLongData($streams); } /** * Handle $this->_longData after regular query parameters have been bound * * @param array<int, resource> $streams * * @throws MysqliException */ private function sendLongData(array $streams): void { foreach ($streams as $paramNr => $stream) { while (! feof($stream)) { $chunk = fread($stream, 8192); if ($chunk === false) { throw FailedReadingStreamOffset::new($paramNr); } if (! $this->_stmt->send_long_data($paramNr - 1, $chunk)) { throw StatementError::new($this->_stmt); } } } } /** * Binds a array of values to bound parameters. * * @param mixed[] $values * * @return bool */ private function bindUntypedValues(array $values) { $params = []; $types = str_repeat('s', count($values)); foreach ($values as &$v) { $params[] =& $v; } return $this->_stmt->bind_param($types, ...$params); } /** * @return mixed[]|false|null * * @throws StatementError */ private function _fetch() { try { $ret = $this->_stmt->fetch(); } catch (mysqli_sql_exception $e) { throw StatementError::upcast($e); } if ($ret === true) { $values = []; foreach ($this->_rowBindedValues as $v) { $values[] = $v; } return $values; } return $ret; } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if (! $this->result) { return false; } $fetchMode = $fetchMode ?: $this->_defaultFetchMode; if ($fetchMode === FetchMode::COLUMN) { return $this->fetchColumn(); } $values = $this->_fetch(); if ($values === null) { return false; } if ($values === false) { throw StatementError::new($this->_stmt); } if ($fetchMode === FetchMode::NUMERIC) { return $values; } assert(is_array($this->_columnNames)); $assoc = array_combine($this->_columnNames, $values); assert(is_array($assoc)); switch ($fetchMode) { case FetchMode::ASSOCIATIVE: return $assoc; case FetchMode::MIXED: return $assoc + $values; case FetchMode::STANDARD_OBJECT: return (object) $assoc; default: throw new MysqliException(sprintf("Unknown fetch type '%s'", $fetchMode)); } } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $fetchMode = $fetchMode ?: $this->_defaultFetchMode; $rows = []; if ($fetchMode === FetchMode::COLUMN) { while (($row = $this->fetchColumn()) !== false) { $rows[] = $row; } } else { while (($row = $this->fetch($fetchMode)) !== false) { $rows[] = $row; } } return $rows; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function fetchNumeric() { // do not try fetching from the statement if it's not expected to contain the result // in order to prevent exceptional situation if (! $this->result) { return false; } $values = $this->_fetch(); if ($values === null) { return false; } if ($values === false) { throw StatementError::new($this->_stmt); } return $values; } /** * {@inheritDoc} */ public function fetchAssociative() { $values = $this->fetchNumeric(); if ($values === false) { return false; } assert(is_array($this->_columnNames)); $row = array_combine($this->_columnNames, $values); assert(is_array($row)); return $row; } /** * {@inheritdoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } /** * {@inheritdoc} */ public function errorCode() { return $this->_stmt->errno; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. * * @return string */ public function errorInfo() { return $this->_stmt->error; } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->free(); return true; } /** * {@inheritdoc} */ public function rowCount() { if ($this->_columnNames === false) { return $this->_stmt->affected_rows; } return $this->_stmt->num_rows; } /** * {@inheritdoc} */ public function columnCount() { return $this->_stmt->field_count; } public function free(): void { $this->_stmt->free_result(); $this->result = false; } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->_defaultFetchMode = $fetchMode; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } } dbal/lib/Doctrine/DBAL/Driver/Mysqli/Statement.php 0000644 00000000141 15120025740 0015643 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\Mysqli; final class Statement extends MysqliStatement { } dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/NonTerminatedStringLiteral.php 0000644 00000000777 15120025740 0022353 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\OCI8\OCI8Exception; use function sprintf; /** * @internal * * @psalm-immutable */ final class NonTerminatedStringLiteral extends OCI8Exception { public static function new(int $offset): self { return new self( sprintf( 'The statement contains non-terminated string literal starting at offset %d.', $offset ) ); } } dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/SequenceDoesNotExist.php 0000644 00000000571 15120025740 0021151 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\OCI8\OCI8Exception; /** * @internal * * @psalm-immutable */ final class SequenceDoesNotExist extends OCI8Exception { public static function new(): self { return new self('lastInsertId failed: Query was executed but no result was returned.'); } } dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/UnknownParameterIndex.php 0000644 00000000702 15120025740 0021354 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\OCI8\Exception; use Doctrine\DBAL\Driver\OCI8\OCI8Exception; use function sprintf; /** * @internal * * @psalm-immutable */ final class UnknownParameterIndex extends OCI8Exception { public static function new(int $index): self { return new self( sprintf('Could not find variable mapping with index %d, in the SQL statement', $index) ); } } dbal/lib/Doctrine/DBAL/Driver/OCI8/Connection.php 0000644 00000000137 15120025740 0015227 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; final class Connection extends OCI8Connection { } dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php 0000644 00000002652 15120025740 0014367 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use const OCI_NO_AUTO_COMMIT; /** * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. */ class Driver extends AbstractOracleDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { return new Connection( (string) $username, (string) $password, $this->_constructDsn($params), $params['charset'] ?? '', $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT, $params['persistent'] ?? false ); } catch (OCI8Exception $e) { throw Exception::driverException($this, $e); } } /** * Constructs the Oracle DSN. * * @param mixed[] $params * * @return string The DSN. */ protected function _constructDsn(array $params) { return $this->getEasyConnectString($params); } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'oci8'; } } dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php 0000644 00000013725 15120025740 0015661 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use UnexpectedValueException; use function addcslashes; use function func_get_args; use function is_float; use function is_int; use function oci_commit; use function oci_connect; use function oci_error; use function oci_pconnect; use function oci_rollback; use function oci_server_version; use function preg_match; use function sprintf; use function str_replace; use const OCI_COMMIT_ON_SUCCESS; use const OCI_NO_AUTO_COMMIT; /** * OCI8 implementation of the Connection interface. * * @deprecated Use {@link Connection} instead */ class OCI8Connection implements ConnectionInterface, ServerInfoAwareConnection { /** @var resource */ protected $dbh; /** @var int */ protected $executeMode = OCI_COMMIT_ON_SUCCESS; /** * Creates a Connection to an Oracle Database using oci8 extension. * * @internal The connection can be only instantiated by its driver. * * @param string $username * @param string $password * @param string $db * @param string $charset * @param int $sessionMode * @param bool $persistent * * @throws OCI8Exception */ public function __construct( $username, $password, $db, $charset = '', $sessionMode = OCI_NO_AUTO_COMMIT, $persistent = false ) { $dbh = $persistent ? @oci_pconnect($username, $password, $db, $charset, $sessionMode) : @oci_connect($username, $password, $db, $charset, $sessionMode); if ($dbh === false) { throw OCI8Exception::fromErrorInfo(oci_error()); } $this->dbh = $dbh; } /** * {@inheritdoc} * * @throws UnexpectedValueException If the version string returned by the database server * does not contain a parsable version number. */ public function getServerVersion() { $version = oci_server_version($this->dbh); if ($version === false) { throw OCI8Exception::fromErrorInfo(oci_error($this->dbh)); } if (! preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches)) { throw new UnexpectedValueException( sprintf( 'Unexpected database version string "%s". Cannot parse an appropriate version number from it. ' . 'Please report this database version string to the Doctrine team.', $version ) ); } return $matches[1]; } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return false; } /** * {@inheritdoc} */ public function prepare($sql) { return new Statement($this->dbh, $sql, $this); } /** * {@inheritdoc} */ public function query() { $args = func_get_args(); $sql = $args[0]; //$fetchMode = $args[1]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } /** * {@inheritdoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value) || is_float($value)) { return $value; } $value = str_replace("'", "''", $value); return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; } /** * {@inheritdoc} */ public function exec($sql) { $stmt = $this->prepare($sql); $stmt->execute(); return $stmt->rowCount(); } /** * {@inheritdoc} * * @param string|null $name * * @return int|false */ public function lastInsertId($name = null) { if ($name === null) { return false; } $sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL'; $stmt = $this->query($sql); $result = $stmt->fetchColumn(); if ($result === false) { throw SequenceDoesNotExist::new(); } return (int) $result; } /** * Returns the current execution mode. * * @internal * * @return int */ public function getExecuteMode() { return $this->executeMode; } /** * {@inheritdoc} */ public function beginTransaction() { $this->executeMode = OCI_NO_AUTO_COMMIT; return true; } /** * {@inheritdoc} */ public function commit() { if (! oci_commit($this->dbh)) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } $this->executeMode = OCI_COMMIT_ON_SUCCESS; return true; } /** * {@inheritdoc} */ public function rollBack() { if (! oci_rollback($this->dbh)) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } $this->executeMode = OCI_COMMIT_ON_SUCCESS; return true; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { $error = oci_error($this->dbh); if ($error !== false) { return $error['code']; } return null; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { $error = oci_error($this->dbh); if ($error === false) { return []; } return $error; } } dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php 0000644 00000001153 15120025740 0015510 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\AbstractDriverException; /** * @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead * * @psalm-immutable */ class OCI8Exception extends AbstractDriverException { /** * @param mixed[]|false $error * * @return OCI8Exception */ public static function fromErrorInfo($error) { if ($error === false) { return new self('Database error occurred but no error information was retrieved from the driver.'); } return new self($error['message'], null, $error['code']); } } dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php 0000644 00000042607 15120025740 0015527 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\OCI8\Exception\NonTerminatedStringLiteral; use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use InvalidArgumentException; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use function array_key_exists; use function assert; use function count; use function implode; use function is_int; use function is_resource; use function oci_bind_by_name; use function oci_cancel; use function oci_error; use function oci_execute; use function oci_fetch_all; use function oci_fetch_array; use function oci_fetch_object; use function oci_new_descriptor; use function oci_num_fields; use function oci_num_rows; use function oci_parse; use function preg_match; use function preg_quote; use function substr; use const OCI_ASSOC; use const OCI_B_BIN; use const OCI_B_BLOB; use const OCI_BOTH; use const OCI_D_LOB; use const OCI_FETCHSTATEMENT_BY_COLUMN; use const OCI_FETCHSTATEMENT_BY_ROW; use const OCI_NUM; use const OCI_RETURN_LOBS; use const OCI_RETURN_NULLS; use const OCI_TEMP_BLOB; use const PREG_OFFSET_CAPTURE; use const SQLT_CHR; /** * The OCI8 implementation of the Statement interface. * * @deprecated Use {@link Statement} instead */ class OCI8Statement implements IteratorAggregate, StatementInterface, Result { /** @var resource */ protected $_dbh; /** @var resource */ protected $_sth; /** @var OCI8Connection */ protected $_conn; /** * @deprecated * * @var string */ protected static $_PARAM = ':param'; /** @var int[] */ protected static $fetchModeMap = [ FetchMode::MIXED => OCI_BOTH, FetchMode::ASSOCIATIVE => OCI_ASSOC, FetchMode::NUMERIC => OCI_NUM, FetchMode::COLUMN => OCI_NUM, ]; /** @var int */ protected $_defaultFetchMode = FetchMode::MIXED; /** @var string[] */ protected $_paramMap = []; /** * Holds references to bound parameter values. * * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected. * * @var mixed[] */ private $boundValues = []; /** * Indicates whether the statement is in the state when fetching results is possible * * @var bool */ private $result = false; /** * Creates a new OCI8Statement that uses the given connection handle and SQL statement. * * @internal The statement can be only instantiated by its driver connection. * * @param resource $dbh The connection handle. * @param string $query The SQL query. */ public function __construct($dbh, $query, OCI8Connection $conn) { [$query, $paramMap] = self::convertPositionalToNamedPlaceholders($query); $stmt = oci_parse($dbh, $query); assert(is_resource($stmt)); $this->_sth = $stmt; $this->_dbh = $dbh; $this->_paramMap = $paramMap; $this->_conn = $conn; } /** * Converts positional (?) into named placeholders (:param<num>). * * Oracle does not support positional parameters, hence this method converts all * positional parameters into artificially named parameters. Note that this conversion * is not perfect. All question marks (?) in the original statement are treated as * placeholders and converted to a named parameter. * * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. * Question marks inside literal strings are therefore handled correctly by this method. * This comes at a cost, the whole sql statement has to be looped over. * * @internal * * @param string $statement The SQL statement to convert. * * @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array). * * @throws OCI8Exception * * @todo extract into utility class in Doctrine\DBAL\Util namespace * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. */ public static function convertPositionalToNamedPlaceholders($statement) { $fragmentOffset = $tokenOffset = 0; $fragments = $paramMap = []; $currentLiteralDelimiter = null; do { if (! $currentLiteralDelimiter) { $result = self::findPlaceholderOrOpeningQuote( $statement, $tokenOffset, $fragmentOffset, $fragments, $currentLiteralDelimiter, $paramMap ); } else { $result = self::findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter); } } while ($result); if ($currentLiteralDelimiter !== null) { throw NonTerminatedStringLiteral::new($tokenOffset - 1); } $fragments[] = substr($statement, $fragmentOffset); $statement = implode('', $fragments); return [$statement, $paramMap]; } /** * Finds next placeholder or opening quote. * * @param string $statement The SQL statement to parse * @param int $tokenOffset The offset to start searching from * @param int $fragmentOffset The offset to build the next fragment from * @param string[] $fragments Fragments of the original statement * not containing placeholders * @param string|null $currentLiteralDelimiter The delimiter of the current string literal * or NULL if not currently in a literal * @param array<int, string> $paramMap Mapping of the original parameter positions * to their named replacements * * @return bool Whether the token was found */ private static function findPlaceholderOrOpeningQuote( $statement, &$tokenOffset, &$fragmentOffset, &$fragments, &$currentLiteralDelimiter, &$paramMap ) { $token = self::findToken($statement, $tokenOffset, '/[?\'"]/'); if (! $token) { return false; } if ($token === '?') { $position = count($paramMap) + 1; $param = ':param' . $position; $fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset); $fragments[] = $param; $paramMap[$position] = $param; $tokenOffset += 1; $fragmentOffset = $tokenOffset; return true; } $currentLiteralDelimiter = $token; ++$tokenOffset; return true; } /** * Finds closing quote * * @param string $statement The SQL statement to parse * @param int $tokenOffset The offset to start searching from * @param string $currentLiteralDelimiter The delimiter of the current string literal * * @return bool Whether the token was found * * @param-out string|null $currentLiteralDelimiter */ private static function findClosingQuote( $statement, &$tokenOffset, &$currentLiteralDelimiter ) { $token = self::findToken( $statement, $tokenOffset, '/' . preg_quote($currentLiteralDelimiter, '/') . '/' ); if (! $token) { return false; } $currentLiteralDelimiter = null; ++$tokenOffset; return true; } /** * Finds the token described by regex starting from the given offset. Updates the offset with the position * where the token was found. * * @param string $statement The SQL statement to parse * @param int $offset The offset to start searching from * @param string $regex The regex containing token pattern * * @return string|null Token or NULL if not found */ private static function findToken($statement, &$offset, $regex) { if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset)) { $offset = $matches[0][1]; return $matches[0][0]; } return null; } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { return $this->bindParam($param, $value, $type, null); } /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { if (is_int($param)) { if (! isset($this->_paramMap[$param])) { throw UnknownParameterIndex::new($param); } $param = $this->_paramMap[$param]; } if ($type === ParameterType::LARGE_OBJECT) { $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB); $lob->writeTemporary($variable, OCI_TEMP_BLOB); $variable =& $lob; } $this->boundValues[$param] =& $variable; return oci_bind_by_name( $this->_sth, $param, $variable, $length ?? -1, $this->convertParameterType($type) ); } /** * Converts DBAL parameter type to oci8 parameter type */ private function convertParameterType(int $type): int { switch ($type) { case ParameterType::BINARY: return OCI_B_BIN; case ParameterType::LARGE_OBJECT: return OCI_B_BLOB; default: return SQLT_CHR; } } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->free(); return true; } /** * {@inheritdoc} */ public function columnCount() { return oci_num_fields($this->_sth) ?: 0; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { $error = oci_error($this->_sth); if ($error !== false) { $error = $error['code']; } return $error; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { $error = oci_error($this->_sth); if ($error === false) { return []; } return $error; } /** * {@inheritdoc} */ public function execute($params = null) { if ($params) { $hasZeroIndex = array_key_exists(0, $params); foreach ($params as $key => $val) { if ($hasZeroIndex && is_int($key)) { $this->bindValue($key + 1, $val); } else { $this->bindValue($key, $val); } } } $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode()); if (! $ret) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } $this->result = true; return $ret; } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->_defaultFetchMode = $fetchMode; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if (! $this->result) { return false; } $fetchMode = $fetchMode ?: $this->_defaultFetchMode; if ($fetchMode === FetchMode::COLUMN) { return $this->fetchColumn(); } if ($fetchMode === FetchMode::STANDARD_OBJECT) { return oci_fetch_object($this->_sth); } if (! isset(self::$fetchModeMap[$fetchMode])) { throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); } return oci_fetch_array( $this->_sth, self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS ); } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $fetchMode = $fetchMode ?: $this->_defaultFetchMode; $result = []; if ($fetchMode === FetchMode::STANDARD_OBJECT) { while ($row = $this->fetch($fetchMode)) { $result[] = $row; } return $result; } if (! isset(self::$fetchModeMap[$fetchMode])) { throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); } if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { while ($row = $this->fetch($fetchMode)) { $result[] = $row; } } else { $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW; if ($fetchMode === FetchMode::COLUMN) { $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; } // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if (! $this->result) { return []; } oci_fetch_all( $this->_sth, $result, 0, -1, self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS ); if ($fetchMode === FetchMode::COLUMN) { $result = $result[0]; } } return $result; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if (! $this->result) { return false; } $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * {@inheritdoc} */ public function rowCount() { return oci_num_rows($this->_sth) ?: 0; } /** * {@inheritdoc} */ public function fetchNumeric() { return $this->doFetch(OCI_NUM); } /** * {@inheritdoc} */ public function fetchAssociative() { return $this->doFetch(OCI_ASSOC); } /** * {@inheritdoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return $this->doFetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return $this->doFetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0]; } public function free(): void { // not having the result means there's nothing to close if (! $this->result) { return; } oci_cancel($this->_sth); $this->result = false; } /** * @return mixed|false */ private function doFetch(int $mode) { // do not try fetching from the statement if it's not expected to contain the result // in order to prevent exceptional situation if (! $this->result) { return false; } return oci_fetch_array( $this->_sth, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS ); } /** * @return array<mixed> */ private function doFetchAll(int $mode, int $fetchStructure): array { // do not try fetching from the statement if it's not expected to contain the result // in order to prevent exceptional situation if (! $this->result) { return []; } oci_fetch_all( $this->_sth, $result, 0, -1, $mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS ); return $result; } } dbal/lib/Doctrine/DBAL/Driver/OCI8/Statement.php 0000644 00000000135 15120025740 0015072 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\OCI8; final class Statement extends OCI8Statement { } dbal/lib/Doctrine/DBAL/Driver/PDO/MySQL/Driver.php 0000644 00000000205 15120025740 0015244 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\MySQL; use Doctrine\DBAL\Driver\PDOMySql; final class Driver extends PDOMySql\Driver { } dbal/lib/Doctrine/DBAL/Driver/PDO/OCI/Driver.php 0000644 00000000205 15120025740 0014711 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\OCI; use Doctrine\DBAL\Driver\PDOOracle; final class Driver extends PDOOracle\Driver { } dbal/lib/Doctrine/DBAL/Driver/PDO/PgSQL/Driver.php 0000644 00000000205 15120025740 0015225 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\PgSQL; use Doctrine\DBAL\Driver\PDOPgSql; final class Driver extends PDOPgSql\Driver { } dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Connection.php 0000644 00000000220 15120025740 0016272 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\PDOSqlsrv; final class Connection extends PDOSqlsrv\Connection { } dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Driver.php 0000644 00000000210 15120025740 0015425 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\PDOSqlsrv; final class Driver extends PDOSqlsrv\Driver { } dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Statement.php 0000644 00000000216 15120025740 0016144 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLSrv; use Doctrine\DBAL\Driver\PDOSqlsrv; final class Statement extends PDOSqlsrv\Statement { } dbal/lib/Doctrine/DBAL/Driver/PDO/SQLite/Driver.php 0000644 00000000210 15120025740 0015434 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO\SQLite; use Doctrine\DBAL\Driver\PDOSqlite; final class Driver extends PDOSqlite\Driver { } dbal/lib/Doctrine/DBAL/Driver/PDO/Connection.php 0000644 00000000200 15120025740 0015136 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\PDOConnection; class Connection extends PDOConnection { } dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php 0000644 00000000471 15120025740 0015007 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\PDOException; /** * @internal * * @psalm-immutable */ final class Exception extends PDOException { public static function new(\PDOException $exception): self { return new self($exception); } } dbal/lib/Doctrine/DBAL/Driver/PDO/Statement.php 0000644 00000000175 15120025740 0015016 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\PDOStatement; class Statement extends PDOStatement { } dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php 0000644 00000003013 15120025740 0014727 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOIbm; use Doctrine\DBAL\Driver\AbstractDB2Driver; use Doctrine\DBAL\Driver\PDO\Connection; use Doctrine\Deprecations\Deprecation; /** * Driver for the PDO IBM extension. * * @deprecated Use the driver based on the ibm_db2 extension instead. */ class Driver extends AbstractDB2Driver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { return new Connection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); } /** * Constructs the IBM PDO DSN. * * @param mixed[] $params * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'ibm:'; if (isset($params['host'])) { $dsn .= 'HOSTNAME=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'PORT=' . $params['port'] . ';'; } $dsn .= 'PROTOCOL=TCPIP;'; if (isset($params['dbname'])) { $dsn .= 'DATABASE=' . $params['dbname'] . ';'; } return $dsn; } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_ibm'; } } dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php 0000644 00000003636 15120025741 0015301 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOMySql; use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use PDOException; /** * PDO MySql driver. * * @deprecated Use {@link PDO\MySQL\Driver} instead. */ class Driver extends AbstractMySQLDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { $conn = new PDO\Connection( $this->constructPdoDsn($params), $username, $password, $driverOptions ); } catch (PDOException $e) { throw Exception::driverException($this, $e); } return $conn; } /** * Constructs the MySql PDO DSN. * * @param mixed[] $params * * @return string The DSN. */ protected function constructPdoDsn(array $params) { $dsn = 'mysql:'; if (isset($params['host']) && $params['host'] !== '') { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } if (isset($params['unix_socket'])) { $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; } if (isset($params['charset'])) { $dsn .= 'charset=' . $params['charset'] . ';'; } return $dsn; } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_mysql'; } } dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php 0000644 00000002711 15120025741 0015432 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOOracle; use Doctrine\DBAL\Driver\AbstractOracleDriver; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use PDOException; /** * PDO Oracle driver. * * @deprecated Use {@link PDO\OCI\Driver} instead. */ class Driver extends AbstractOracleDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { return new PDO\Connection( $this->constructPdoDsn($params), $username, $password, $driverOptions ); } catch (PDOException $e) { throw Exception::driverException($this, $e); } } /** * Constructs the Oracle PDO DSN. * * @param mixed[] $params * * @return string The DSN. */ private function constructPdoDsn(array $params) { $dsn = 'oci:dbname=' . $this->getEasyConnectString($params); if (isset($params['charset'])) { $dsn .= ';charset=' . $params['charset']; } return $dsn; } /** * {@inheritdoc} */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_oracle'; } } dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php 0000644 00000007237 15120025741 0015263 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOPgSql; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use PDOException; use function defined; /** * Driver that connects through pdo_pgsql. * * @deprecated Use {@link PDO\PgSQL\Driver} instead. */ class Driver extends AbstractPostgreSQLDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { $pdo = new PDO\Connection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); if ( defined('PDO::PGSQL_ATTR_DISABLE_PREPARES') && (! isset($driverOptions[\PDO::PGSQL_ATTR_DISABLE_PREPARES]) || $driverOptions[\PDO::PGSQL_ATTR_DISABLE_PREPARES] === true ) ) { $pdo->setAttribute(\PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } /* defining client_encoding via SET NAMES to avoid inconsistent DSN support * - the 'client_encoding' connection param only works with postgres >= 9.1 * - passing client_encoding via the 'options' param breaks pgbouncer support */ if (isset($params['charset'])) { $pdo->exec('SET NAMES \'' . $params['charset'] . '\''); } return $pdo; } catch (PDOException $e) { throw Exception::driverException($this, $e); } } /** * Constructs the Postgres PDO DSN. * * @param mixed[] $params * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'pgsql:'; if (isset($params['host']) && $params['host'] !== '') { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port']) && $params['port'] !== '') { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } elseif (isset($params['default_dbname'])) { $dsn .= 'dbname=' . $params['default_dbname'] . ';'; } else { // Used for temporary connections to allow operations like dropping the database currently connected to. // Connecting without an explicit database does not work, therefore "postgres" database is used // as it is mostly present in every server setup. $dsn .= 'dbname=postgres;'; } if (isset($params['sslmode'])) { $dsn .= 'sslmode=' . $params['sslmode'] . ';'; } if (isset($params['sslrootcert'])) { $dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';'; } if (isset($params['sslcert'])) { $dsn .= 'sslcert=' . $params['sslcert'] . ';'; } if (isset($params['sslkey'])) { $dsn .= 'sslkey=' . $params['sslkey'] . ';'; } if (isset($params['sslcrl'])) { $dsn .= 'sslcrl=' . $params['sslcrl'] . ';'; } if (isset($params['application_name'])) { $dsn .= 'application_name=' . $params['application_name'] . ';'; } return $dsn; } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_pgsql'; } } dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php 0000644 00000004570 15120025741 0015473 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOSqlite; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\Deprecations\Deprecation; use PDOException; use function array_merge; /** * The PDO Sqlite driver. * * @deprecated Use {@link PDO\SQLite\Driver} instead. */ class Driver extends AbstractSQLiteDriver { /** @var mixed[] */ protected $_userDefinedFunctions = [ 'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1], 'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2], 'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1], ]; /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { if (isset($driverOptions['userDefinedFunctions'])) { $this->_userDefinedFunctions = array_merge( $this->_userDefinedFunctions, $driverOptions['userDefinedFunctions'] ); unset($driverOptions['userDefinedFunctions']); } try { $pdo = new PDO\Connection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); } catch (PDOException $ex) { throw Exception::driverException($this, $ex); } foreach ($this->_userDefinedFunctions as $fn => $data) { $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']); } return $pdo; } /** * Constructs the Sqlite PDO DSN. * * @param mixed[] $params * * @return string The DSN. */ protected function _constructPdoDsn(array $params) { $dsn = 'sqlite:'; if (isset($params['path'])) { $dsn .= $params['path']; } elseif (isset($params['memory'])) { $dsn .= ':memory:'; } return $dsn; } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_sqlite'; } } dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php 0000644 00000002334 15120025741 0016364 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOSqlsrv; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\Result; /** * Sqlsrv Connection implementation. * * @deprecated Use {@link PDO\SQLSrv\Connection} instead. */ class Connection extends PDO\Connection { /** * {@inheritdoc} * * @internal The connection can be only instantiated by its driver. * * @param string $dsn * @param string|null $user * @param string|null $password * @param mixed[]|null $options */ public function __construct($dsn, $user = null, $password = null, ?array $options = null) { parent::__construct($dsn, $user, $password, $options); $this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, [PDO\SQLSrv\Statement::class, []]); } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name === null) { return parent::lastInsertId($name); } $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); $stmt->execute([$name]); if ($stmt instanceof Result) { return $stmt->fetchOne(); } return $stmt->fetchColumn(); } } dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php 0000644 00000005300 15120025741 0015514 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOSqlsrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\DBAL\Driver\PDO; use Doctrine\Deprecations\Deprecation; use function is_int; use function sprintf; /** * The PDO-based Sqlsrv driver. * * @deprecated Use {@link PDO\SQLSrv\Driver} instead. */ class Driver extends AbstractSQLServerDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { $pdoOptions = $dsnOptions = []; foreach ($driverOptions as $option => $value) { if (is_int($option)) { $pdoOptions[$option] = $value; } else { $dsnOptions[$option] = $value; } } return new PDO\SQLSrv\Connection( $this->_constructPdoDsn($params, $dsnOptions), $username, $password, $pdoOptions ); } /** * Constructs the Sqlsrv PDO DSN. * * @param mixed[] $params * @param string[] $connectionOptions * * @return string The DSN. */ private function _constructPdoDsn(array $params, array $connectionOptions) { $dsn = 'sqlsrv:server='; if (isset($params['host'])) { $dsn .= $params['host']; if (isset($params['port'])) { $dsn .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } if (isset($params['dbname'])) { $connectionOptions['Database'] = $params['dbname']; } if (isset($params['MultipleActiveResultSets'])) { $connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false'; } return $dsn . $this->getConnectionOptionsDsn($connectionOptions); } /** * Converts a connection options array to the DSN * * @param string[] $connectionOptions */ private function getConnectionOptionsDsn(array $connectionOptions): string { $connectionOptionsDsn = ''; foreach ($connectionOptions as $paramName => $paramValue) { $connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue); } return $connectionOptionsDsn; } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'pdo_sqlsrv'; } } dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php 0000644 00000002246 15120025741 0016233 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\PDOSqlsrv; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\ParameterType; /** * PDO SQL Server Statement * * @deprecated Use {@link PDO\SQLSrv\Statement} instead. */ class Statement extends PDO\Statement { /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) { switch ($type) { case ParameterType::LARGE_OBJECT: case ParameterType::BINARY: if ($driverOptions === null) { $driverOptions = \PDO::SQLSRV_ENCODING_BINARY; } break; case ParameterType::ASCII: $type = ParameterType::STRING; $length = 0; $driverOptions = \PDO::SQLSRV_ENCODING_SYSTEM; break; } return parent::bindParam($param, $variable, $type, $length ?? 0, $driverOptions); } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { return $this->bindParam($param, $value, $type); } } dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php 0000644 00000006362 15120025741 0016032 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLAnywhere; use Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver; use Doctrine\DBAL\Exception; use Doctrine\Deprecations\Deprecation; use function array_keys; use function array_map; use function implode; /** * A Doctrine DBAL driver for the SAP Sybase SQL Anywhere PHP extension. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class Driver extends AbstractSQLAnywhereDriver { /** * {@inheritdoc} * * @throws Exception If there was a problem establishing the connection. */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { try { return new SQLAnywhereConnection( $this->buildDsn( $params['host'] ?? null, $params['port'] ?? null, $params['server'] ?? null, $params['dbname'] ?? null, $username, $password, $driverOptions ), $params['persistent'] ?? false ); } catch (SQLAnywhereException $e) { throw Exception::driverException($this, $e); } } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'sqlanywhere'; } /** * Build the connection string for given connection parameters and driver options. * * @param string|null $host Host address to connect to. * @param int|null $port Port to use for the connection (default to SQL Anywhere standard port 2638). * @param string|null $server Database server name on the host to connect to. * SQL Anywhere allows multiple database server instances on the same host, * therefore specifying the server instance name to use is mandatory. * @param string|null $dbname Name of the database on the server instance to connect to. * @param string $username User name to use for connection authentication. * @param string $password Password to use for connection authentication. * @param mixed[] $driverOptions Additional parameters to use for the connection. * * @return string */ private function buildDsn( $host, $port, $server, $dbname, $username = null, $password = null, array $driverOptions = [] ) { $host = $host ?: 'localhost'; $port = $port ?: 2638; if (! empty($server)) { $server = ';ServerName=' . $server; } return 'HOST=' . $host . ':' . $port . $server . ';DBN=' . $dbname . ';UID=' . $username . ';PWD=' . $password . ';' . implode( ';', array_map(static function ($key, $value) { return $key . '=' . $value; }, array_keys($driverOptions), $driverOptions) ); } } dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php 0000644 00000013776 15120025741 0020770 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLAnywhere; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function assert; use function func_get_args; use function is_float; use function is_int; use function is_resource; use function is_string; use function sasql_affected_rows; use function sasql_commit; use function sasql_connect; use function sasql_error; use function sasql_errorcode; use function sasql_escape_string; use function sasql_insert_id; use function sasql_pconnect; use function sasql_real_query; use function sasql_rollback; use function sasql_set_option; /** * SAP Sybase SQL Anywhere implementation of the Connection interface. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywhereConnection implements Connection, ServerInfoAwareConnection { /** @var resource The SQL Anywhere connection resource. */ private $connection; /** * Connects to database with given connection string. * * @internal The connection can be only instantiated by its driver. * * @param string $dsn The connection string. * @param bool $persistent Whether or not to establish a persistent connection. * * @throws SQLAnywhereException */ public function __construct($dsn, $persistent = false) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4077', 'The SQLAnywhere driver is deprecated' ); $this->connection = $persistent ? @sasql_pconnect($dsn) : @sasql_connect($dsn); if (! is_resource($this->connection)) { throw SQLAnywhereException::fromSQLAnywhereError(); } // Disable PHP warnings on error. if (! sasql_set_option($this->connection, 'verbose_errors', false)) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } // Enable auto committing by default. if (! sasql_set_option($this->connection, 'auto_commit', 'on')) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } } /** * {@inheritdoc} * * @throws SQLAnywhereException */ public function beginTransaction() { if (! sasql_set_option($this->connection, 'auto_commit', 'off')) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } return true; } /** * {@inheritdoc} * * @throws SQLAnywhereException */ public function commit() { if (! sasql_commit($this->connection)) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } $this->endTransaction(); return true; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { return sasql_errorcode($this->connection); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return sasql_error($this->connection); } /** * {@inheritdoc} */ public function exec($sql) { if (sasql_real_query($this->connection, $sql) === false) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } return sasql_affected_rows($this->connection); } /** * {@inheritdoc} */ public function getServerVersion() { $stmt = $this->query("SELECT PROPERTY('ProductVersion')"); if ($stmt instanceof Result) { $version = $stmt->fetchOne(); } else { $version = $stmt->fetchColumn(); } assert(is_string($version)); return $version; } /** * {@inheritdoc} */ public function lastInsertId($name = null) { if ($name === null) { return sasql_insert_id($this->connection); } $stmt = $this->query('SELECT ' . $name . '.CURRVAL'); if ($stmt instanceof Result) { return $stmt->fetchOne(); } return $stmt->fetchColumn(); } /** * {@inheritdoc} */ public function prepare($sql) { return new SQLAnywhereStatement($this->connection, $sql); } /** * {@inheritdoc} */ public function query() { $args = func_get_args(); $stmt = $this->prepare($args[0]); $stmt->execute(); return $stmt; } /** * {@inheritdoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value) || is_float($value)) { return $value; } return "'" . sasql_escape_string($this->connection, $value) . "'"; } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return true; } /** * {@inheritdoc} * * @throws SQLAnywhereException */ public function rollBack() { if (! sasql_rollback($this->connection)) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } $this->endTransaction(); return true; } /** * Ends transactional mode and enables auto commit again. * * @return bool Whether or not ending transactional mode succeeded. * * @throws SQLAnywhereException */ private function endTransaction() { if (! sasql_set_option($this->connection, 'auto_commit', 'on')) { throw SQLAnywhereException::fromSQLAnywhereError($this->connection); } return true; } } dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php 0000644 00000004642 15120025741 0020617 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLAnywhere; use Doctrine\DBAL\Driver\AbstractDriverException; use InvalidArgumentException; use function sasql_error; use function sasql_errorcode; use function sasql_sqlstate; use function sasql_stmt_errno; use function sasql_stmt_error; /** * SAP Sybase SQL Anywhere driver exception. * * @deprecated Support for SQLAnywhere will be removed in 3.0. * * @psalm-immutable */ class SQLAnywhereException extends AbstractDriverException { /** * Helper method to turn SQL Anywhere error into exception. * * @param resource|null $conn The SQL Anywhere connection resource to retrieve the last error from. * @param resource|null $stmt The SQL Anywhere statement resource to retrieve the last error from. * * @return SQLAnywhereException * * @throws InvalidArgumentException */ public static function fromSQLAnywhereError($conn = null, $stmt = null) { $state = $conn ? sasql_sqlstate($conn) : sasql_sqlstate(); $code = null; $message = null; /** * Try retrieving the last error from statement resource if given */ if ($stmt) { $code = sasql_stmt_errno($stmt); $message = sasql_stmt_error($stmt); } /** * Try retrieving the last error from the connection resource * if either the statement resource is not given or the statement * resource is given but the last error could not be retrieved from it (fallback). * Depending on the type of error, it is sometimes necessary to retrieve * it from the connection resource even though it occurred during * a prepared statement. */ if ($conn && ! $code) { $code = sasql_errorcode($conn); $message = sasql_error($conn); } /** * Fallback mode if either no connection resource is given * or the last error could not be retrieved from the given * connection / statement resource. */ if (! $conn || ! $code) { $code = sasql_errorcode(); $message = sasql_error(); } if ($message) { return new self('SQLSTATE [' . $state . '] [' . $code . '] ' . $message, $state, $code); } return new self('SQL Anywhere error occurred but no error message was retrieved from driver.', $state, $code); } } dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php 0000644 00000030414 15120025741 0020621 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLAnywhere; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use IteratorAggregate; use PDO; use ReflectionClass; use ReflectionObject; use ReturnTypeWillChange; use stdClass; use function array_key_exists; use function assert; use function func_get_args; use function func_num_args; use function gettype; use function is_array; use function is_int; use function is_object; use function is_resource; use function is_string; use function sasql_fetch_array; use function sasql_fetch_assoc; use function sasql_fetch_object; use function sasql_fetch_row; use function sasql_prepare; use function sasql_stmt_affected_rows; use function sasql_stmt_bind_param_ex; use function sasql_stmt_errno; use function sasql_stmt_error; use function sasql_stmt_execute; use function sasql_stmt_field_count; use function sasql_stmt_reset; use function sasql_stmt_result_metadata; use function sprintf; use const SASQL_BOTH; /** * SAP SQL Anywhere implementation of the Statement interface. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywhereStatement implements IteratorAggregate, Statement, Result { /** @var resource The connection resource. */ private $conn; /** @var string Name of the default class to instantiate when fetching class instances. */ private $defaultFetchClass = '\stdClass'; /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ private $defaultFetchClassCtorArgs = []; /** @var int Default fetch mode to use. */ private $defaultFetchMode = FetchMode::MIXED; /** @var resource|null The result set resource to fetch. */ private $result; /** @var resource The prepared SQL statement to execute. */ private $stmt; /** @var mixed[] The references to bound parameter values. */ private $boundValues = []; /** * Prepares given statement for given connection. * * @internal The statement can be only instantiated by its driver connection. * * @param resource $conn The connection resource to use. * @param string $sql The SQL statement to prepare. * * @throws SQLAnywhereException */ public function __construct($conn, $sql) { if (! is_resource($conn)) { throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn); } $this->conn = $conn; $this->stmt = sasql_prepare($conn, $sql); if (! is_resource($this->stmt)) { throw SQLAnywhereException::fromSQLAnywhereError($conn); } } /** * {@inheritdoc} * * @throws SQLAnywhereException */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { assert(is_int($param)); switch ($type) { case ParameterType::INTEGER: case ParameterType::BOOLEAN: $type = 'i'; break; case ParameterType::LARGE_OBJECT: $type = 'b'; break; case ParameterType::NULL: case ParameterType::STRING: case ParameterType::BINARY: $type = 's'; break; default: throw new SQLAnywhereException('Unknown type: ' . $type); } $this->boundValues[$param] =& $variable; if (! sasql_stmt_bind_param_ex($this->stmt, $param - 1, $variable, $type, $variable === null)) { throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); } return true; } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { assert(is_int($param)); return $this->bindParam($param, $value, $type); } /** * {@inheritdoc} * * @deprecated Use free() instead. * * @throws SQLAnywhereException */ public function closeCursor() { if (! sasql_stmt_reset($this->stmt)) { throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); } return true; } /** * {@inheritdoc} */ public function columnCount() { return sasql_stmt_field_count($this->stmt); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { return sasql_stmt_errno($this->stmt); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return sasql_stmt_error($this->stmt); } /** * {@inheritdoc} * * @throws SQLAnywhereException */ public function execute($params = null) { if (is_array($params)) { $hasZeroIndex = array_key_exists(0, $params); foreach ($params as $key => $val) { if ($hasZeroIndex && is_int($key)) { $this->bindValue($key + 1, $val); } else { $this->bindValue($key, $val); } } } if (! sasql_stmt_execute($this->stmt)) { throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); } $this->result = sasql_stmt_result_metadata($this->stmt); return true; } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. * * @throws SQLAnywhereException */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { if (! is_resource($this->result)) { return false; } $fetchMode = $fetchMode ?: $this->defaultFetchMode; switch ($fetchMode) { case FetchMode::COLUMN: return $this->fetchColumn(); case FetchMode::ASSOCIATIVE: return sasql_fetch_assoc($this->result); case FetchMode::MIXED: return sasql_fetch_array($this->result, SASQL_BOTH); case FetchMode::CUSTOM_OBJECT: $className = $this->defaultFetchClass; $ctorArgs = $this->defaultFetchClassCtorArgs; if (func_num_args() >= 2) { $args = func_get_args(); $className = $args[1]; $ctorArgs = $args[2] ?? []; } $result = sasql_fetch_object($this->result); if ($result instanceof stdClass) { $result = $this->castObject($result, $className, $ctorArgs); } return $result; case FetchMode::NUMERIC: return sasql_fetch_row($this->result); case FetchMode::STANDARD_OBJECT: return sasql_fetch_object($this->result); default: throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode); } } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $rows = []; switch ($fetchMode) { case FetchMode::CUSTOM_OBJECT: while (($row = $this->fetch(...func_get_args())) !== false) { $rows[] = $row; } break; case FetchMode::COLUMN: while (($row = $this->fetchColumn()) !== false) { $rows[] = $row; } break; default: while (($row = $this->fetch($fetchMode)) !== false) { $rows[] = $row; } } return $rows; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } /** * {@inheritDoc} */ public function fetchNumeric() { if (! is_resource($this->result)) { return false; } return sasql_fetch_row($this->result); } /** * {@inheritdoc} */ public function fetchAssociative() { if (! is_resource($this->result)) { return false; } return sasql_fetch_assoc($this->result); } /** * {@inheritdoc} * * @throws Exception */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * @return array<int,array<int,mixed>> * * @throws Exception */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * @return array<int,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * @return array<int,mixed> * * @throws Exception */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } /** * {@inheritdoc} */ public function rowCount() { return sasql_stmt_affected_rows($this->stmt); } public function free(): void { sasql_stmt_reset($this->stmt); } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->defaultFetchMode = $fetchMode; $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; return true; } /** * Casts a stdClass object to the given class name mapping its' properties. * * @param stdClass $sourceObject Object to cast from. * @param class-string|object $destinationClass Name of the class or class instance to cast to. * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. * * @return object * * @throws SQLAnywhereException */ private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) { if (! is_string($destinationClass)) { if (! is_object($destinationClass)) { throw new SQLAnywhereException(sprintf( 'Destination class has to be of type string or object, %s given.', gettype($destinationClass) )); } } else { $destinationClass = new ReflectionClass($destinationClass); $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); } $sourceReflection = new ReflectionObject($sourceObject); $destinationClassReflection = new ReflectionObject($destinationClass); foreach ($sourceReflection->getProperties() as $sourceProperty) { $sourceProperty->setAccessible(true); $name = $sourceProperty->getName(); $value = $sourceProperty->getValue($sourceObject); if ($destinationClassReflection->hasProperty($name)) { $destinationProperty = $destinationClassReflection->getProperty($name); $destinationProperty->setAccessible(true); $destinationProperty->setValue($destinationClass, $value); } else { $destinationClass->$name = $value; } } return $destinationClass; } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Exception/Error.php 0000644 00000002042 15120025741 0016605 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver\SQLSrv\Exception; use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException; use function rtrim; use function sqlsrv_errors; use const SQLSRV_ERR_ERRORS; /** * @internal * * @psalm-immutable */ final class Error extends SQLSrvException { public static function new(): self { $message = ''; $sqlState = null; $errorCode = null; foreach ((array) sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error) { $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n"; if ($sqlState === null) { $sqlState = $error['SQLSTATE']; } if ($errorCode !== null) { continue; } $errorCode = $error['code']; } if (! $message) { $message = 'SQL Server error occurred but no error message was retrieved from driver.'; } return new self(rtrim($message), $sqlState, $errorCode); } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Connection.php 0000644 00000000143 15120025741 0015655 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; final class Connection extends SQLSrvConnection { } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php 0000644 00000003215 15120025741 0015014 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; use Doctrine\Deprecations\Deprecation; /** * Driver for ext/sqlsrv. */ class Driver extends AbstractSQLServerDriver { /** * {@inheritdoc} */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { $serverName = ''; if (isset($params['host'])) { $serverName = $params['host']; if (isset($params['port'])) { $serverName .= ',' . $params['port']; } } elseif (isset($params['port'])) { throw PortWithoutHost::new(); } if (isset($params['dbname'])) { $driverOptions['Database'] = $params['dbname']; } if (isset($params['charset'])) { $driverOptions['CharacterSet'] = $params['charset']; } if ($username !== null) { $driverOptions['UID'] = $username; } if ($password !== null) { $driverOptions['PWD'] = $password; } if (! isset($driverOptions['ReturnDatesAsStrings'])) { $driverOptions['ReturnDatesAsStrings'] = 1; } return new Connection($serverName, $driverOptions); } /** * {@inheritdoc} * * @deprecated */ public function getName() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Driver::getName() is deprecated' ); return 'sqlsrv'; } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php 0000644 00000000612 15120025741 0016124 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; /** * Last Id Data Container. * * @internal */ class LastInsertId { /** @var int */ private $id; /** * @param int $id * * @return void */ public function setId($id) { $this->id = $id; } /** * @return int */ public function getId() { return $this->id; } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php 0000644 00000011465 15120025741 0016741 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use function func_get_args; use function is_float; use function is_int; use function sprintf; use function sqlsrv_begin_transaction; use function sqlsrv_commit; use function sqlsrv_configure; use function sqlsrv_connect; use function sqlsrv_errors; use function sqlsrv_query; use function sqlsrv_rollback; use function sqlsrv_rows_affected; use function sqlsrv_server_info; use function str_replace; use const SQLSRV_ERR_ERRORS; /** * SQL Server implementation for the Connection interface. * * @deprecated Use {@link Connection} instead */ class SQLSrvConnection implements ConnectionInterface, ServerInfoAwareConnection { /** @var resource */ protected $conn; /** @var LastInsertId */ protected $lastInsertId; /** * @internal The connection can be only instantiated by its driver. * * @param string $serverName * @param mixed[] $connectionOptions * * @throws SQLSrvException */ public function __construct($serverName, $connectionOptions) { if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) { throw Error::new(); } $conn = sqlsrv_connect($serverName, $connectionOptions); if ($conn === false) { throw Error::new(); } $this->conn = $conn; $this->lastInsertId = new LastInsertId(); } /** * {@inheritdoc} */ public function getServerVersion() { $serverInfo = sqlsrv_server_info($this->conn); return $serverInfo['SQLServerVersion']; } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return false; } /** * {@inheritDoc} */ public function prepare($sql) { return new Statement($this->conn, $sql, $this->lastInsertId); } /** * {@inheritDoc} */ public function query() { $args = func_get_args(); $sql = $args[0]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } /** * {@inheritDoc} */ public function quote($value, $type = ParameterType::STRING) { if (is_int($value)) { return $value; } if (is_float($value)) { return sprintf('%F', $value); } return "'" . str_replace("'", "''", $value) . "'"; } /** * {@inheritDoc} */ public function exec($sql) { $stmt = sqlsrv_query($this->conn, $sql); if ($stmt === false) { throw Error::new(); } $rowsAffected = sqlsrv_rows_affected($stmt); if ($rowsAffected === false) { throw Error::new(); } return $rowsAffected; } /** * {@inheritDoc} */ public function lastInsertId($name = null) { if ($name !== null) { $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); $stmt->execute([$name]); } else { $stmt = $this->query('SELECT @@IDENTITY'); } if ($stmt instanceof Result) { return $stmt->fetchOne(); } return $stmt->fetchColumn(); } /** * {@inheritDoc} */ public function beginTransaction() { if (! sqlsrv_begin_transaction($this->conn)) { throw Error::new(); } return true; } /** * {@inheritDoc} */ public function commit() { if (! sqlsrv_commit($this->conn)) { throw Error::new(); } return true; } /** * {@inheritDoc} */ public function rollBack() { if (! sqlsrv_rollback($this->conn)) { throw Error::new(); } return true; } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); if ($errors) { return $errors[0]['code']; } return null; } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php 0000644 00000000773 15120025741 0016600 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\AbstractDriverException; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; /** * @deprecated Use {@link \Doctrine\DBAL\Driver\Exception} instead * * @psalm-immutable */ class SQLSrvException extends AbstractDriverException { /** * Helper method to turn sql server errors into exception. * * @return SQLSrvException */ public static function fromSqlSrvErrors() { return Error::new(); } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php 0000644 00000032027 15120025741 0016603 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\SQLSrv\Exception\Error; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use function array_key_exists; use function count; use function func_get_args; use function in_array; use function is_int; use function is_numeric; use function sqlsrv_errors; use function sqlsrv_execute; use function sqlsrv_fetch; use function sqlsrv_fetch_array; use function sqlsrv_fetch_object; use function sqlsrv_get_field; use function sqlsrv_next_result; use function sqlsrv_num_fields; use function SQLSRV_PHPTYPE_STREAM; use function SQLSRV_PHPTYPE_STRING; use function sqlsrv_prepare; use function sqlsrv_rows_affected; use function SQLSRV_SQLTYPE_VARBINARY; use function stripos; use const SQLSRV_ENC_BINARY; use const SQLSRV_ENC_CHAR; use const SQLSRV_ERR_ERRORS; use const SQLSRV_FETCH_ASSOC; use const SQLSRV_FETCH_BOTH; use const SQLSRV_FETCH_NUMERIC; use const SQLSRV_PARAM_IN; /** * SQL Server Statement. * * @deprecated Use {@link Statement} instead */ class SQLSrvStatement implements IteratorAggregate, StatementInterface, Result { /** * The SQLSRV Resource. * * @var resource */ private $conn; /** * The SQL statement to execute. * * @var string */ private $sql; /** * The SQLSRV statement resource. * * @var resource|null */ private $stmt; /** * References to the variables bound as statement parameters. * * @var mixed */ private $variables = []; /** * Bound parameter types. * * @var int[] */ private $types = []; /** * Translations. * * @var int[] */ private static $fetchMap = [ FetchMode::MIXED => SQLSRV_FETCH_BOTH, FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC, FetchMode::NUMERIC => SQLSRV_FETCH_NUMERIC, ]; /** * The name of the default class to instantiate when fetching class instances. * * @var string */ private $defaultFetchClass = '\stdClass'; /** * The constructor arguments for the default class to instantiate when fetching class instances. * * @var mixed[] */ private $defaultFetchClassCtorArgs = []; /** * The fetch style. * * @var int */ private $defaultFetchMode = FetchMode::MIXED; /** * The last insert ID. * * @var LastInsertId|null */ private $lastInsertId; /** * Indicates whether the statement is in the state when fetching results is possible * * @var bool */ private $result = false; /** * Append to any INSERT query to retrieve the last insert id. * * @deprecated This constant has been deprecated and will be made private in 3.0 */ public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; /** * @internal The statement can be only instantiated by its driver connection. * * @param resource $conn * @param string $sql */ public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null) { $this->conn = $conn; $this->sql = $sql; if (stripos($sql, 'INSERT INTO ') !== 0) { return; } $this->sql .= self::LAST_INSERT_ID_SQL; $this->lastInsertId = $lastInsertId; } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { if (! is_numeric($param)) { throw new SQLSrvException( 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.' ); } $this->variables[$param] = $value; $this->types[$param] = $type; return true; } /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { if (! is_numeric($param)) { throw new SQLSrvException( 'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.' ); } $this->variables[$param] =& $variable; $this->types[$param] = $type; // unset the statement resource if it exists as the new one will need to be bound to the new variable $this->stmt = null; return true; } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { $this->free(); return true; } /** * {@inheritdoc} */ public function columnCount() { if ($this->stmt === null) { return 0; } return sqlsrv_num_fields($this->stmt) ?: 0; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); if ($errors) { return $errors[0]['code']; } return false; } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS); } /** * {@inheritdoc} */ public function execute($params = null) { if ($params) { $hasZeroIndex = array_key_exists(0, $params); foreach ($params as $key => $val) { if ($hasZeroIndex && is_int($key)) { $this->bindValue($key + 1, $val); } else { $this->bindValue($key, $val); } } } if (! $this->stmt) { $this->stmt = $this->prepare(); } if (! sqlsrv_execute($this->stmt)) { throw Error::new(); } if ($this->lastInsertId) { sqlsrv_next_result($this->stmt); sqlsrv_fetch($this->stmt); $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); } $this->result = true; return true; } /** * Prepares SQL Server statement resource * * @return resource * * @throws SQLSrvException */ private function prepare() { $params = []; foreach ($this->variables as $column => &$variable) { switch ($this->types[$column]) { case ParameterType::LARGE_OBJECT: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max'), ]; break; case ParameterType::BINARY: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY), ]; break; case ParameterType::ASCII: $params[$column - 1] = [ &$variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), ]; break; default: $params[$column - 1] =& $variable; break; } } $stmt = sqlsrv_prepare($this->conn, $this->sql, $params); if (! $stmt) { throw Error::new(); } return $stmt; } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->defaultFetchMode = $fetchMode; $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; return true; } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. * * @throws SQLSrvException */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { // do not try fetching from the statement if it's not expected to contain result // in order to prevent exceptional situation if ($this->stmt === null || ! $this->result) { return false; } $args = func_get_args(); $fetchMode = $fetchMode ?: $this->defaultFetchMode; if ($fetchMode === FetchMode::COLUMN) { return $this->fetchColumn(); } if (isset(self::$fetchMap[$fetchMode])) { return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false; } if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) { $className = $this->defaultFetchClass; $ctorArgs = $this->defaultFetchClassCtorArgs; if (count($args) >= 2) { $className = $args[1]; $ctorArgs = $args[2] ?? []; } return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false; } throw new SQLSrvException('Fetch mode is not supported!'); } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $rows = []; switch ($fetchMode) { case FetchMode::CUSTOM_OBJECT: while (($row = $this->fetch(...func_get_args())) !== false) { $rows[] = $row; } break; case FetchMode::COLUMN: while (($row = $this->fetchColumn()) !== false) { $rows[] = $row; } break; default: while (($row = $this->fetch($fetchMode)) !== false) { $rows[] = $row; } } return $rows; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $row = $this->fetch(FetchMode::NUMERIC); if ($row === false) { return false; } return $row[$columnIndex] ?? null; } /** * {@inheritdoc} */ public function fetchNumeric() { return $this->doFetch(SQLSRV_FETCH_NUMERIC); } /** * {@inheritdoc} */ public function fetchAssociative() { return $this->doFetch(SQLSRV_FETCH_ASSOC); } /** * {@inheritdoc} */ public function fetchOne() { return FetchUtils::fetchOne($this); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return FetchUtils::fetchAllNumeric($this); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return FetchUtils::fetchAllAssociative($this); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return FetchUtils::fetchFirstColumn($this); } /** * {@inheritdoc} */ public function rowCount() { if ($this->stmt === null) { return 0; } return sqlsrv_rows_affected($this->stmt) ?: 0; } public function free(): void { // not having the result means there's nothing to close if ($this->stmt === null || ! $this->result) { return; } // emulate it by fetching and discarding rows, similarly to what PDO does in this case // @link http://php.net/manual/en/pdostatement.closecursor.php // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them while (sqlsrv_fetch($this->stmt)) { } $this->result = false; } /** * @return mixed|false */ private function doFetch(int $fetchType) { // do not try fetching from the statement if it's not expected to contain the result // in order to prevent exceptional situation if ($this->stmt === null || ! $this->result) { return false; } return sqlsrv_fetch_array($this->stmt, $fetchType) ?? false; } } dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php 0000644 00000000141 15120025741 0015520 0 ustar 00 <?php namespace Doctrine\DBAL\Driver\SQLSrv; final class Statement extends SQLSrvStatement { } dbal/lib/Doctrine/DBAL/Driver/AbstractDB2Driver.php 0000644 00000002245 15120025741 0015640 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as TheDriverException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Schema\DB2SchemaManager; /** * Abstract base implementation of the {@link Driver} interface for IBM DB2 based drivers. */ abstract class AbstractDB2Driver implements Driver { /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new DB2Platform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new DB2SchemaManager($conn); } /** * @param string $message * * @return DriverException */ public function convertException($message, TheDriverException $exception) { return new DriverException($message, $exception); } } dbal/lib/Doctrine/DBAL/Driver/AbstractDriverException.php 0000644 00000000222 15120025741 0017220 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * @deprecated * * @psalm-immutable */ class AbstractDriverException extends AbstractException { } dbal/lib/Doctrine/DBAL/Driver/AbstractException.php 0000644 00000003137 15120025741 0016054 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Doctrine\Deprecations\Deprecation; use Exception as BaseException; use Throwable; /** * Base implementation of the {@link Exception} interface. * * @internal * * @psalm-immutable */ abstract class AbstractException extends BaseException implements DriverException { /** * The driver specific error code. * * @var int|string|null */ private $errorCode; /** * The SQLSTATE of the driver. * * @var string|null */ private $sqlState; /** * @param string $message The driver error message. * @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any. * @param int|string|null $errorCode The driver specific error code if any. */ public function __construct($message, $sqlState = null, $errorCode = null, ?Throwable $previous = null) { parent::__construct($message, 0, $previous); $this->errorCode = $errorCode; $this->sqlState = $sqlState; } /** * {@inheritdoc} */ public function getErrorCode() { /** @psalm-suppress ImpureMethodCall */ Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4112', 'Driver\AbstractException::getErrorCode() is deprecated, use getSQLState() or getCode() instead.' ); return $this->errorCode; } /** * {@inheritdoc} */ public function getSQLState() { return $this->sqlState; } } dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php 0000644 00000017135 15120025741 0016242 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\ConnectionLost; use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\MySqlSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use function assert; use function preg_match; use function stripos; use function version_compare; /** * Abstract base implementation of the {@link Driver} interface for MySQL based drivers. */ abstract class AbstractMySQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver { /** * {@inheritdoc} * * @deprecated * * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html * @link https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html */ public function convertException($message, DeprecatedDriverException $exception) { switch ($exception->getErrorCode()) { case '1213': return new DeadlockException($message, $exception); case '1205': return new LockWaitTimeoutException($message, $exception); case '1050': return new TableExistsException($message, $exception); case '1051': case '1146': return new TableNotFoundException($message, $exception); case '1216': case '1217': case '1451': case '1452': case '1701': return new ForeignKeyConstraintViolationException($message, $exception); case '1062': case '1557': case '1569': case '1586': return new UniqueConstraintViolationException($message, $exception); case '1054': case '1166': case '1611': return new InvalidFieldNameException($message, $exception); case '1052': case '1060': case '1110': return new NonUniqueFieldNameException($message, $exception); case '1064': case '1149': case '1287': case '1341': case '1342': case '1343': case '1344': case '1382': case '1479': case '1541': case '1554': case '1626': return new SyntaxErrorException($message, $exception); case '1044': case '1045': case '1046': case '1049': case '1095': case '1142': case '1143': case '1227': case '1370': case '1429': case '2002': case '2005': return new ConnectionException($message, $exception); case '2006': return new ConnectionLost($message, $exception); case '1048': case '1121': case '1138': case '1171': case '1252': case '1263': case '1364': case '1566': return new NotNullConstraintViolationException($message, $exception); } return new DriverException($message, $exception); } /** * {@inheritdoc} * * @throws Exception */ public function createDatabasePlatformForVersion($version) { $mariadb = stripos($version, 'mariadb') !== false; if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) { return new MariaDb1027Platform(); } if (! $mariadb) { $oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version); if (version_compare($oracleMysqlVersion, '8', '>=')) { return new MySQL80Platform(); } if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) { return new MySQL57Platform(); } } return $this->getDatabasePlatform(); } /** * Get a normalized 'version number' from the server string * returned by Oracle MySQL servers. * * @param string $versionString Version string returned by the driver, i.e. '5.7.10' * * @throws Exception */ private function getOracleMysqlVersionNumber(string $versionString): string { if ( ! preg_match( '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $versionString, $versionParts ) ) { throw Exception::invalidPlatformVersionSpecified( $versionString, '<major_version>.<minor_version>.<patch_version>' ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? null; if ($majorVersion === '5' && $minorVersion === '7' && $patchVersion === null) { $patchVersion = '9'; } return $majorVersion . '.' . $minorVersion . '.' . $patchVersion; } /** * Detect MariaDB server version, including hack for some mariadb distributions * that starts with the prefix '5.5.5-' * * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' * * @throws Exception */ private function getMariaDbMysqlVersionNumber(string $versionString): string { if ( ! preg_match( '/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i', $versionString, $versionParts ) ) { throw Exception::invalidPlatformVersionSpecified( $versionString, '^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>' ); } return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); if (isset($params['dbname'])) { return $params['dbname']; } $database = $conn->query('SELECT DATABASE()')->fetchColumn(); assert($database !== false); return $database; } /** * {@inheritdoc} * * @return MySqlPlatform */ public function getDatabasePlatform() { return new MySqlPlatform(); } /** * {@inheritdoc} * * @return MySqlSchemaManager */ public function getSchemaManager(Connection $conn) { return new MySqlSchemaManager($conn); } } dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php 0000644 00000006344 15120025741 0016502 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\OracleSchemaManager; /** * Abstract base implementation of the {@link Driver} interface for Oracle based drivers. */ abstract class AbstractOracleDriver implements Driver, ExceptionConverterDriver { /** * {@inheritdoc} * * @deprecated */ public function convertException($message, DeprecatedDriverException $exception) { switch ($exception->getErrorCode()) { case '1': case '2299': case '38911': return new UniqueConstraintViolationException($message, $exception); case '904': return new InvalidFieldNameException($message, $exception); case '918': case '960': return new NonUniqueFieldNameException($message, $exception); case '923': return new SyntaxErrorException($message, $exception); case '942': return new TableNotFoundException($message, $exception); case '955': return new TableExistsException($message, $exception); case '1017': case '12545': return new ConnectionException($message, $exception); case '1400': return new NotNullConstraintViolationException($message, $exception); case '2266': case '2291': case '2292': return new ForeignKeyConstraintViolationException($message, $exception); } return new DriverException($message, $exception); } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); return $params['user']; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new OraclePlatform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new OracleSchemaManager($conn); } /** * Returns an appropriate Easy Connect String for the given parameters. * * @param mixed[] $params The connection parameters to return the Easy Connect String for. * * @return string */ protected function getEasyConnectString(array $params) { return (string) EasyConnectString::fromConnectionParameters($params); } } dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php 0000644 00000013314 15120025741 0017273 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\PostgreSQL100Platform; use Doctrine\DBAL\Platforms\PostgreSQL91Platform; use Doctrine\DBAL\Platforms\PostgreSQL92Platform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Schema\PostgreSqlSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use function assert; use function preg_match; use function strpos; use function version_compare; /** * Abstract base implementation of the {@link Driver} interface for PostgreSQL based drivers. */ abstract class AbstractPostgreSQLDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver { /** * {@inheritdoc} * * @deprecated * * @link http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html */ public function convertException($message, DeprecatedDriverException $exception) { $sqlState = $exception->getSQLState(); switch ($sqlState) { case '40001': case '40P01': return new DeadlockException($message, $exception); case '0A000': // Foreign key constraint violations during a TRUNCATE operation // are considered "feature not supported" in PostgreSQL. if (strpos($exception->getMessage(), 'truncate') !== false) { return new ForeignKeyConstraintViolationException($message, $exception); } break; case '23502': return new NotNullConstraintViolationException($message, $exception); case '23503': return new ForeignKeyConstraintViolationException($message, $exception); case '23505': return new UniqueConstraintViolationException($message, $exception); case '42601': return new SyntaxErrorException($message, $exception); case '42702': return new NonUniqueFieldNameException($message, $exception); case '42703': return new InvalidFieldNameException($message, $exception); case '42P01': return new TableNotFoundException($message, $exception); case '42P07': return new TableExistsException($message, $exception); case '08006': return new Exception\ConnectionException($message, $exception); case '7': // Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.3.22 and PHP 7.4.10), // in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code. // The exception code would be always set to 7 here. // We have to match against the SQLSTATE in the error message in these cases. if (strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) { return new ConnectionException($message, $exception); } break; } return new DriverException($message, $exception); } /** * {@inheritdoc} */ public function createDatabasePlatformForVersion($version) { if (! preg_match('/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $version, $versionParts)) { throw Exception::invalidPlatformVersionSpecified( $version, '<major_version>.<minor_version>.<patch_version>' ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? 0; $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion; switch (true) { case version_compare($version, '10.0', '>='): return new PostgreSQL100Platform(); case version_compare($version, '9.4', '>='): return new PostgreSQL94Platform(); case version_compare($version, '9.2', '>='): return new PostgreSQL92Platform(); case version_compare($version, '9.1', '>='): return new PostgreSQL91Platform(); default: return new PostgreSqlPlatform(); } } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); if (isset($params['dbname'])) { return $params['dbname']; } $database = $conn->query('SELECT CURRENT_DATABASE()')->fetchColumn(); assert($database !== false); return $database; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new PostgreSqlPlatform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new PostgreSqlSchemaManager($conn); } } dbal/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php 0000644 00000012216 15120025741 0017432 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DeadlockException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\SQLAnywhere11Platform; use Doctrine\DBAL\Platforms\SQLAnywhere12Platform; use Doctrine\DBAL\Platforms\SQLAnywhere16Platform; use Doctrine\DBAL\Platforms\SQLAnywherePlatform; use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use function assert; use function preg_match; use function version_compare; /** * Abstract base implementation of the {@link Driver} interface for SAP Sybase SQL Anywhere based drivers. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ abstract class AbstractSQLAnywhereDriver implements Driver, ExceptionConverterDriver, VersionAwarePlatformDriver { /** * {@inheritdoc} * * @deprecated * * @link http://dcx.sybase.com/index.html#sa160/en/saerrors/sqlerror.html */ public function convertException($message, DeprecatedDriverException $exception) { switch ($exception->getErrorCode()) { case '-306': case '-307': case '-684': return new DeadlockException($message, $exception); case '-210': case '-1175': case '-1281': return new LockWaitTimeoutException($message, $exception); case '-100': case '-103': case '-832': return new ConnectionException($message, $exception); case '-143': return new InvalidFieldNameException($message, $exception); case '-193': case '-196': return new UniqueConstraintViolationException($message, $exception); case '-194': case '-198': return new ForeignKeyConstraintViolationException($message, $exception); case '-144': return new NonUniqueFieldNameException($message, $exception); case '-184': case '-195': return new NotNullConstraintViolationException($message, $exception); case '-131': return new SyntaxErrorException($message, $exception); case '-110': return new TableExistsException($message, $exception); case '-141': case '-1041': return new TableNotFoundException($message, $exception); } return new DriverException($message, $exception); } /** * {@inheritdoc} */ public function createDatabasePlatformForVersion($version) { if ( ! preg_match( '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+)(?:\.(?P<build>\d+))?)?)?/', $version, $versionParts ) ) { throw Exception::invalidPlatformVersionSpecified( $version, '<major_version>.<minor_version>.<patch_version>.<build_version>' ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? 0; $buildVersion = $versionParts['build'] ?? 0; $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion; switch (true) { case version_compare($version, '16', '>='): return new SQLAnywhere16Platform(); case version_compare($version, '12', '>='): return new SQLAnywhere12Platform(); case version_compare($version, '11', '>='): return new SQLAnywhere11Platform(); default: return new SQLAnywherePlatform(); } } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); if (isset($params['dbname'])) { return $params['dbname']; } $database = $conn->query('SELECT DB_NAME()')->fetchColumn(); assert($database !== false); return $database; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new SQLAnywhere12Platform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new SQLAnywhereSchemaManager($conn); } } dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver.php 0000644 00000005753 15120025741 0017126 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as TheDriverException; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\SQLServer2005Platform; use Doctrine\DBAL\Platforms\SQLServer2008Platform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Schema\SQLServerSchemaManager; use Doctrine\DBAL\VersionAwarePlatformDriver; use function assert; use function preg_match; use function version_compare; /** * Abstract base implementation of the {@link Driver} interface for Microsoft SQL Server based drivers. */ abstract class AbstractSQLServerDriver implements Driver, VersionAwarePlatformDriver { /** * {@inheritdoc} */ public function createDatabasePlatformForVersion($version) { if ( ! preg_match( '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+)(?:\.(?P<build>\d+))?)?)?/', $version, $versionParts ) ) { throw Exception::invalidPlatformVersionSpecified( $version, '<major_version>.<minor_version>.<patch_version>.<build_version>' ); } $majorVersion = $versionParts['major']; $minorVersion = $versionParts['minor'] ?? 0; $patchVersion = $versionParts['patch'] ?? 0; $buildVersion = $versionParts['build'] ?? 0; $version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion; switch (true) { case version_compare($version, '11.00.2100', '>='): return new SQLServer2012Platform(); case version_compare($version, '10.00.1600', '>='): return new SQLServer2008Platform(); case version_compare($version, '9.00.1399', '>='): return new SQLServer2005Platform(); default: return new SQLServerPlatform(); } } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); if (isset($params['dbname'])) { return $params['dbname']; } $database = $conn->query('SELECT DB_NAME()')->fetchColumn(); assert($database !== false); return $database; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new SQLServer2008Platform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new SQLServerSchemaManager($conn); } /** * @param string $message * * @return DriverException */ public function convertException($message, TheDriverException $exception) { return new DriverException($message, $exception); } } dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php 0000644 00000007421 15120025741 0016433 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception\ConnectionException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Exception\InvalidFieldNameException; use Doctrine\DBAL\Exception\LockWaitTimeoutException; use Doctrine\DBAL\Exception\NonUniqueFieldNameException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; use Doctrine\DBAL\Exception\ReadOnlyException; use Doctrine\DBAL\Exception\SyntaxErrorException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\SqliteSchemaManager; use function strpos; /** * Abstract base implementation of the {@link Driver} interface for SQLite based drivers. */ abstract class AbstractSQLiteDriver implements Driver, ExceptionConverterDriver { /** * {@inheritdoc} * * @deprecated * * @link http://www.sqlite.org/c3ref/c_abort.html */ public function convertException($message, DeprecatedDriverException $exception) { if (strpos($exception->getMessage(), 'database is locked') !== false) { return new LockWaitTimeoutException($message, $exception); } if ( strpos($exception->getMessage(), 'must be unique') !== false || strpos($exception->getMessage(), 'is not unique') !== false || strpos($exception->getMessage(), 'are not unique') !== false || strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false ) { return new UniqueConstraintViolationException($message, $exception); } if ( strpos($exception->getMessage(), 'may not be NULL') !== false || strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false ) { return new NotNullConstraintViolationException($message, $exception); } if (strpos($exception->getMessage(), 'no such table:') !== false) { return new TableNotFoundException($message, $exception); } if (strpos($exception->getMessage(), 'already exists') !== false) { return new TableExistsException($message, $exception); } if (strpos($exception->getMessage(), 'has no column named') !== false) { return new InvalidFieldNameException($message, $exception); } if (strpos($exception->getMessage(), 'ambiguous column name') !== false) { return new NonUniqueFieldNameException($message, $exception); } if (strpos($exception->getMessage(), 'syntax error') !== false) { return new SyntaxErrorException($message, $exception); } if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) { return new ReadOnlyException($message, $exception); } if (strpos($exception->getMessage(), 'unable to open database file') !== false) { return new ConnectionException($message, $exception); } return new DriverException($message, $exception); } /** * {@inheritdoc} * * @deprecated Use Connection::getDatabase() instead. */ public function getDatabase(Connection $conn) { $params = $conn->getParams(); return $params['path'] ?? null; } /** * {@inheritdoc} */ public function getDatabasePlatform() { return new SqlitePlatform(); } /** * {@inheritdoc} */ public function getSchemaManager(Connection $conn) { return new SqliteSchemaManager($conn); } } dbal/lib/Doctrine/DBAL/Driver/Connection.php 0000644 00000004335 15120025741 0014532 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Connection interface. * Driver connections must implement this interface. * * This resembles (a subset of) the PDO interface. */ interface Connection { /** * Prepares a statement for execution and returns a Statement object. * * @param string $sql * * @return Statement */ public function prepare($sql); /** * Executes an SQL statement, returning a result set as a Statement object. * * @return Statement */ public function query(); /** * Quotes a string for use in a query. * * @param mixed $value * @param int $type * * @return mixed */ public function quote($value, $type = ParameterType::STRING); /** * Executes an SQL statement and return the number of affected rows. * * @param string $sql * * @return int|string */ public function exec($sql); /** * Returns the ID of the last inserted row or sequence value. * * @param string|null $name * * @return string|int|false */ public function lastInsertId($name = null); /** * Initiates a transaction. * * @return bool TRUE on success or FALSE on failure. */ public function beginTransaction(); /** * Commits a transaction. * * @return bool TRUE on success or FALSE on failure. */ public function commit(); /** * Rolls back the current transaction, as initiated by beginTransaction(). * * @return bool TRUE on success or FALSE on failure. */ public function rollBack(); /** * Returns the error code associated with the last operation on the database handle. * * @deprecated The error information is available via exceptions. * * @return string|null The error code, or null if no operation has been run on the database handle. */ public function errorCode(); /** * Returns extended error information associated with the last operation on the database handle. * * @deprecated The error information is available via exceptions. * * @return mixed[] */ public function errorInfo(); } dbal/lib/Doctrine/DBAL/Driver/DriverException.php 0000644 00000000244 15120025741 0015540 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * @deprecated Use {@link Exception} instead * * @psalm-immutable */ interface DriverException extends Exception { } dbal/lib/Doctrine/DBAL/Driver/Exception.php 0000644 00000001364 15120025741 0014370 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; use Throwable; /** * @psalm-immutable */ interface Exception extends Throwable { /** * Returns the driver specific error code if available. * * @deprecated Use {@link getCode()} or {@link getSQLState()} instead * * Returns null if no driver specific error code is available * for the error raised by the driver. * * @return int|string|null */ public function getErrorCode(); /** * Returns the SQLSTATE the driver was in at the time the error occurred. * * Returns null if the driver does not provide a SQLSTATE for the error occurred. * * @return string|null */ public function getSQLState(); } dbal/lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php 0000644 00000001666 15120025741 0017441 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\DriverException as TheDriverException; use Doctrine\DBAL\Exception\DriverException; /** * Contract for a driver that is capable of converting DBAL driver exceptions into standardized DBAL driver exceptions. * * @deprecated */ interface ExceptionConverterDriver { /** * Converts a given DBAL driver exception into a standardized DBAL driver exception. * * It evaluates the vendor specific error code and SQLSTATE and transforms * it into a unified {@link DriverException} subclass. * * @deprecated * * @param string $message The DBAL exception message to use. * @param TheDriverException $exception The DBAL driver exception to convert. * * @return DriverException An instance of one of the DriverException subclasses. */ public function convertException($message, TheDriverException $exception); } dbal/lib/Doctrine/DBAL/Driver/FetchUtils.php 0000644 00000002451 15120025741 0014502 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** * @internal */ final class FetchUtils { /** * @return mixed|false * * @throws Exception */ public static function fetchOne(Result $result) { $row = $result->fetchNumeric(); if ($row === false) { return false; } return $row[0]; } /** * @return array<int,array<int,mixed>> * * @throws Exception */ public static function fetchAllNumeric(Result $result): array { $rows = []; while (($row = $result->fetchNumeric()) !== false) { $rows[] = $row; } return $rows; } /** * @return array<int,array<string,mixed>> * * @throws Exception */ public static function fetchAllAssociative(Result $result): array { $rows = []; while (($row = $result->fetchAssociative()) !== false) { $rows[] = $row; } return $rows; } /** * @return array<int,mixed> * * @throws Exception */ public static function fetchFirstColumn(Result $result): array { $rows = []; while (($row = $result->fetchOne()) !== false) { $rows[] = $row; } return $rows; } } dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php 0000644 00000007214 15120025741 0015074 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Statement; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use PDOStatement; use ReturnTypeWillChange; use function assert; /** * PDO implementation of the Connection interface. * Used by all PDO-based drivers. * * @deprecated Use {@link Connection} instead */ class PDOConnection extends PDO implements ConnectionInterface, ServerInfoAwareConnection { use PDOQueryImplementation; /** * @internal The connection can be only instantiated by its driver. * * @param string $dsn * @param string|null $user * @param string|null $password * @param mixed[]|null $options * * @throws PDOException In case of an error. */ public function __construct($dsn, $user = null, $password = null, ?array $options = null) { try { parent::__construct($dsn, (string) $user, (string) $password, (array) $options); $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class, []]); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} */ #[ReturnTypeWillChange] public function exec($sql) { try { $result = parent::exec($sql); assert($result !== false); return $result; } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} */ public function getServerVersion() { return PDO::getAttribute(PDO::ATTR_SERVER_VERSION); } /** * @param string $sql * @param array<int, int> $driverOptions * * @return PDOStatement */ #[ReturnTypeWillChange] public function prepare($sql, $driverOptions = []) { try { $statement = parent::prepare($sql, $driverOptions); assert($statement instanceof PDOStatement); return $statement; } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} */ #[ReturnTypeWillChange] public function quote($value, $type = ParameterType::STRING) { return parent::quote($value, $type); } /** * {@inheritdoc} * * @param string|null $name * * @return string|int|false */ #[ReturnTypeWillChange] public function lastInsertId($name = null) { try { if ($name === null) { return parent::lastInsertId(); } return parent::lastInsertId($name); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} */ public function requiresQueryForServerVersion() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4114', 'ServerInfoAwareConnection::requiresQueryForServerVersion() is deprecated and removed in DBAL 3.' ); return false; } /** * @param mixed ...$args */ private function doQuery(...$args): PDOStatement { try { $stmt = parent::query(...$args); } catch (PDOException $exception) { throw Exception::new($exception); } assert($stmt instanceof PDOStatement); return $stmt; } } dbal/lib/Doctrine/DBAL/Driver/PDOException.php 0000644 00000002744 15120025741 0014736 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\Deprecations\Deprecation; /** * @deprecated Use {@link Exception} instead * * @psalm-immutable */ class PDOException extends \PDOException implements DriverException { /** * The driver specific error code. * * @var int|string|null */ private $errorCode; /** * The SQLSTATE of the driver. * * @var string|null */ private $sqlState; /** * @param \PDOException $exception The PDO exception to wrap. */ public function __construct(\PDOException $exception) { parent::__construct($exception->getMessage(), 0, $exception); $this->code = $exception->getCode(); $this->errorInfo = $exception->errorInfo; $this->errorCode = $exception->errorInfo[1] ?? $exception->getCode(); $this->sqlState = $exception->errorInfo[0] ?? $exception->getCode(); } /** * {@inheritdoc} */ public function getErrorCode() { /** @psalm-suppress ImpureMethodCall */ Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4112', 'Driver\AbstractException::getErrorCode() is deprecated, use getSQLState() or getCode() instead.' ); return $this->errorCode; } /** * {@inheritdoc} */ public function getSQLState() { return $this->sqlState; } } dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php 0000644 00000001422 15120025741 0017003 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use PDOStatement; use ReturnTypeWillChange; use function func_get_args; use const PHP_VERSION_ID; if (PHP_VERSION_ID >= 80000) { /** * @internal */ trait PDOQueryImplementation { /** * @return PDOStatement */ #[ReturnTypeWillChange] public function query(?string $query = null, ?int $fetchMode = null, mixed ...$fetchModeArgs) { return $this->doQuery($query, $fetchMode, ...$fetchModeArgs); } } } else { /** * @internal */ trait PDOQueryImplementation { /** * @return PDOStatement */ public function query() { return $this->doQuery(...func_get_args()); } } } dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php 0000644 00000017453 15120025741 0014747 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\Statement as StatementInterface; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use PDO; use PDOException; use ReturnTypeWillChange; use function array_slice; use function assert; use function func_get_args; use function is_array; /** * The PDO implementation of the Statement interface. * Used by all PDO-based drivers. * * @deprecated Use {@link Statement} instead */ class PDOStatement extends \PDOStatement implements StatementInterface, Result { use PDOStatementImplementations; private const PARAM_TYPE_MAP = [ ParameterType::NULL => PDO::PARAM_NULL, ParameterType::INTEGER => PDO::PARAM_INT, ParameterType::STRING => PDO::PARAM_STR, ParameterType::ASCII => PDO::PARAM_STR, ParameterType::BINARY => PDO::PARAM_LOB, ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, ParameterType::BOOLEAN => PDO::PARAM_BOOL, ]; private const FETCH_MODE_MAP = [ FetchMode::ASSOCIATIVE => PDO::FETCH_ASSOC, FetchMode::NUMERIC => PDO::FETCH_NUM, FetchMode::MIXED => PDO::FETCH_BOTH, FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ, FetchMode::COLUMN => PDO::FETCH_COLUMN, FetchMode::CUSTOM_OBJECT => PDO::FETCH_CLASS, ]; /** * Protected constructor. * * @internal The statement can be only instantiated by its driver connection. */ protected function __construct() { } /** * {@inheritdoc} */ #[ReturnTypeWillChange] public function bindValue($param, $value, $type = ParameterType::STRING) { $type = $this->convertParamType($type); try { return parent::bindValue($param, $value, $type); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * @param mixed $param * @param mixed $variable * @param int $type * @param int|null $length * @param mixed $driverOptions * * @return bool */ #[ReturnTypeWillChange] public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) { $type = $this->convertParamType($type); try { return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} * * @deprecated Use free() instead. */ #[ReturnTypeWillChange] public function closeCursor() { try { return parent::closeCursor(); } catch (PDOException $exception) { // Exceptions not allowed by the interface. // In case driver implementations do not adhere to the interface, silence exceptions here. return true; } } /** * {@inheritdoc} */ #[ReturnTypeWillChange] public function execute($params = null) { try { return parent::execute($params); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ #[ReturnTypeWillChange] public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { $args = func_get_args(); if (isset($args[0])) { $args[0] = $this->convertFetchMode($args[0]); } try { return parent::fetch(...$args); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ #[ReturnTypeWillChange] public function fetchColumn($columnIndex = 0) { try { return parent::fetchColumn($columnIndex); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * {@inheritdoc} */ public function fetchNumeric() { return $this->fetch(PDO::FETCH_NUM); } /** * {@inheritdoc} */ public function fetchAssociative() { return $this->fetch(PDO::FETCH_ASSOC); } /** * {@inheritdoc} */ public function fetchOne() { return $this->fetch(PDO::FETCH_COLUMN); } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { return $this->fetchAll(PDO::FETCH_NUM); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { return $this->fetchAll(PDO::FETCH_ASSOC); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { return $this->fetchAll(PDO::FETCH_COLUMN); } public function free(): void { parent::closeCursor(); } /** * @param mixed ...$args */ private function doSetFetchMode(int $fetchMode, ...$args): bool { $fetchMode = $this->convertFetchMode($fetchMode); // This thin wrapper is necessary to shield against the weird signature // of PDOStatement::setFetchMode(): even if the second and third // parameters are optional, PHP will not let us remove it from this // declaration. $slice = []; foreach ($args as $arg) { if ($arg === null) { break; } $slice[] = $arg; } try { return parent::setFetchMode($fetchMode, ...$slice); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * @param mixed ...$args * * @return mixed[] */ private function doFetchAll(...$args): array { if (isset($args[0])) { $args[0] = $this->convertFetchMode($args[0]); } $slice = []; foreach ($args as $arg) { if ($arg === null) { break; } $slice[] = $arg; } try { $data = parent::fetchAll(...$slice); } catch (PDOException $exception) { throw Exception::new($exception); } assert(is_array($data)); return $data; } /** * Converts DBAL parameter type to PDO parameter type * * @param int $type Parameter type */ private function convertParamType(int $type): int { if (! isset(self::PARAM_TYPE_MAP[$type])) { // TODO: next major: throw an exception Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3088', 'Using a PDO parameter type (%d given) is deprecated, ' . 'use \Doctrine\DBAL\Types\Types constants instead.', $type ); return $type; } return self::PARAM_TYPE_MAP[$type]; } /** * Converts DBAL fetch mode to PDO fetch mode * * @param int $fetchMode Fetch mode */ private function convertFetchMode(int $fetchMode): int { if (! isset(self::FETCH_MODE_MAP[$fetchMode])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3088', 'Using an unsupported PDO fetch mode or a bitmask of fetch modes (%d given)' . ' is deprecated and will cause an error in Doctrine DBAL 3.0', $fetchMode ); return $fetchMode; } return self::FETCH_MODE_MAP[$fetchMode]; } } dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php 0000644 00000003621 15120025741 0020030 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use ReturnTypeWillChange; use function func_get_args; use const PHP_VERSION_ID; if (PHP_VERSION_ID >= 80000) { /** * @internal */ trait PDOStatementImplementations { /** * @deprecated Use one of the fetch- or iterate-related methods. * * @param int $mode * @param mixed ...$args * * @return bool */ #[ReturnTypeWillChange] public function setFetchMode($mode, ...$args) { return $this->doSetFetchMode($mode, ...$args); } /** * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. * * @param int|null $mode * @param mixed ...$args * * @return mixed[] */ #[ReturnTypeWillChange] public function fetchAll($mode = null, ...$args) { return $this->doFetchAll($mode, ...$args); } } } else { /** * @internal */ trait PDOStatementImplementations { /** * @deprecated Use one of the fetch- or iterate-related methods. * * @param int $fetchMode * @param mixed $arg2 * @param mixed $arg3 */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool { return $this->doSetFetchMode(...func_get_args()); } /** * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. * * @param int|null $fetchMode * @param mixed $fetchArgument * @param mixed $ctorArgs * * @return mixed[] */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { return $this->doFetchAll(...func_get_args()); } } } dbal/lib/Doctrine/DBAL/Driver/PingableConnection.php 0000644 00000000605 15120025741 0016170 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * An interface for connections which support a "native" ping method. * * @deprecated */ interface PingableConnection { /** * Pings the database server to determine if the connection is still * available. Return true/false based on if that was successful or not. * * @return bool */ public function ping(); } dbal/lib/Doctrine/DBAL/Driver/Result.php 0000644 00000004636 15120025741 0013715 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Driver; /** * Driver-level result statement execution result. */ interface Result { /** * Returns the next row of the result as a numeric array or FALSE if there are no more rows. * * @return array<int,mixed>|false * * @throws Exception */ public function fetchNumeric(); /** * Returns the next row of the result as an associative array or FALSE if there are no more rows. * * @return array<string,mixed>|false * * @throws Exception */ public function fetchAssociative(); /** * Returns the first value of the next row of the result or FALSE if there are no more rows. * * @return mixed|false * * @throws Exception */ public function fetchOne(); /** * Returns an array containing all of the result rows represented as numeric arrays. * * @return array<int,array<int,mixed>> * * @throws Exception */ public function fetchAllNumeric(): array; /** * Returns an array containing all of the result rows represented as associative arrays. * * @return array<int,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(): array; /** * Returns an array containing the values of the first column of the result. * * @return array<int,mixed> * * @throws Exception */ public function fetchFirstColumn(): array; /** * Returns the number of rows affected by the DELETE, INSERT, or UPDATE statement that produced the result. * * If the statement executed a SELECT query or a similar platform-specific SQL (e.g. DESCRIBE, SHOW, etc.), * some database drivers may return the number of rows returned by that query. However, this behaviour * is not guaranteed for all drivers and should not be relied on in portable applications. * * @return int|string The number of rows. */ public function rowCount(); /** * Returns the number of columns in the result * * @return int The number of columns in the result. If the columns cannot be counted, * this method must return 0. */ public function columnCount(); /** * Discards the non-fetched portion of the result, enabling the originating statement to be executed again. */ public function free(): void; } dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php 0000644 00000013077 15120025741 0015601 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use PDO; use Traversable; /** * Interface for the reading part of a prepare statement only. */ interface ResultStatement extends Traversable { /** * Closes the cursor, enabling the statement to be executed again. * * @deprecated Use Result::free() instead. * * @return bool TRUE on success or FALSE on failure. */ public function closeCursor(); /** * Returns the number of columns in the result set * * @return int The number of columns in the result set represented * by the PDOStatement object. If there is no result set, * this method should return 0. */ public function columnCount(); /** * Sets the fetch mode to use while iterating this statement. * * @deprecated Use one of the fetch- or iterate-related methods. * * @param int $fetchMode The fetch mode must be one of the {@link FetchMode} constants. * @param mixed $arg2 * @param mixed $arg3 * * @return bool */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null); /** * Returns the next row of a result set. * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. * * @param int|null $fetchMode Controls how the next row will be returned to the caller. * The value must be one of the {@link FetchMode} constants, * defaulting to {@link FetchMode::MIXED}. * @param int $cursorOrientation For a ResultStatement object representing a scrollable cursor, * this value determines which row will be returned to the caller. * This value must be one of the \PDO::FETCH_ORI_* constants, * defaulting to \PDO::FETCH_ORI_NEXT. To request a scrollable * cursor for your ResultStatement object, you must set the \PDO::ATTR_CURSOR * attribute to \PDO::CURSOR_SCROLL when you prepare the SQL statement with * \PDO::prepare(). * @param int $cursorOffset For a ResultStatement object representing a scrollable cursor for which the * cursorOrientation parameter is set to \PDO::FETCH_ORI_ABS, this value * specifies the absolute number of the row in the result set that shall be * fetched. * For a ResultStatement object representing a scrollable cursor for which the * cursorOrientation parameter is set to \PDO::FETCH_ORI_REL, this value * specifies the row to fetch relative to the cursor position before * ResultStatement::fetch() was called. * * @return mixed The return value of this method on success depends on the fetch mode. In all cases, FALSE is * returned on failure. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0); /** * Returns an array containing all of the result set rows. * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. * * @param int|null $fetchMode Controls how the next row will be returned to the caller. * The value must be one of the {@link FetchMode} constants, * defaulting to {@link FetchMode::MIXED}. * @param int|string|null $fetchArgument This argument has a different meaning depending on the value * of the $fetchMode parameter: * * {@link FetchMode::COLUMN}: * Returns the indicated 0-indexed column. * * {@link FetchMode::CUSTOM_OBJECT}: * Returns instances of the specified class, mapping the columns of each row * to named properties in the class. * * {@link PDO::FETCH_FUNC}: Returns the results of calling * the specified function, using each row's * columns as parameters in the call. * @param mixed[]|null $ctorArgs Controls how the next row will be returned to the caller. * The value must be one of the {@link FetchMode} constants, * defaulting to {@link FetchMode::MIXED}. * * @return mixed[] */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null); /** * Returns a single column from the next row of a result set or FALSE if there are no more rows. * * @deprecated Use fetchOne() instead. * * @param int $columnIndex 0-indexed number of the column you wish to retrieve from the row. * If no value is supplied, fetches the first column. * * @return mixed|false A single column in the next row of a result set, or FALSE if there are no more rows. */ public function fetchColumn($columnIndex = 0); } dbal/lib/Doctrine/DBAL/Driver/ServerInfoAwareConnection.php 0000644 00000001176 15120025741 0017515 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; /** * Contract for a connection that is able to provide information about the server it is connected to. */ interface ServerInfoAwareConnection { /** * Returns the version number of the database server connected to. * * @return string */ public function getServerVersion(); /** * Checks whether a query is required to retrieve the database server version. * * @deprecated * * @return bool True if a query is required to retrieve the database server version, false otherwise. */ public function requiresQueryForServerVersion(); } dbal/lib/Doctrine/DBAL/Driver/Statement.php 0000644 00000012402 15120025741 0014371 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; /** * Statement interface. * Drivers must implement this interface. * * This resembles (a subset of) the PDOStatement interface. */ interface Statement extends ResultStatement { /** * Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional * placeholder in the SQL statement that was used to prepare the statement. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * @param int|string $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $value The value to bind to the parameter. * @param int $type Explicit data type for the parameter using the {@link ParameterType} * constants. * * @return bool TRUE on success or FALSE on failure. */ public function bindValue($param, $value, $type = ParameterType::STRING); /** * Binds a PHP variable to a corresponding named (not supported by mysqli driver, see comment below) or question * mark placeholder in the SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), * the variable is bound as a reference and will only be evaluated at the time * that PDOStatement->execute() is called. * * As mentioned above, the named parameters are not natively supported by the mysqli driver, use executeQuery(), * fetchAll(), fetchArray(), fetchColumn(), fetchAssoc() methods to have the named parameter emulated by doctrine. * * Most parameters are input parameters, that is, parameters that are * used in a read-only fashion to build up the query. Some drivers support the invocation * of stored procedures that return data as output parameters, and some also as input/output * parameters that both send in data and are updated to receive it. * * @param int|string $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement using * question mark placeholders, this will be the 1-indexed position of the parameter. * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * @param int $type Explicit data type for the parameter using the {@link ParameterType} * constants. To return an INOUT parameter from a stored procedure, use the bitwise * OR operator to set the PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. * @param int|null $length You must specify maxlength when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success or FALSE on failure. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null); /** * Fetches the SQLSTATE associated with the last operation on the statement handle. * * @deprecated The error information is available via exceptions. * * @see Doctrine_Adapter_Interface::errorCode() * * @return string|int|bool The error code string. */ public function errorCode(); /** * Fetches extended error information associated with the last operation on the statement handle. * * @deprecated The error information is available via exceptions. * * @return mixed[] The error info array. */ public function errorInfo(); /** * Executes a prepared statement * * If the prepared statement included parameter markers, you must either: * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: * bound variables pass their value as input and receive the output value, * if any, of their associated parameter markers or pass an array of input-only * parameter values. * * @param mixed[]|null $params An array of values with as many elements as there are * bound parameters in the SQL statement being executed. * * @return bool TRUE on success or FALSE on failure. */ public function execute($params = null); /** * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement * executed by the corresponding object. * * If the last SQL statement executed by the associated Statement object was a SELECT statement, * some databases may return the number of rows returned by that statement. However, * this behaviour is not guaranteed for all databases and should not be * relied on for portable applications. * * @return int|string The number of rows. */ public function rowCount(); } dbal/lib/Doctrine/DBAL/Driver/StatementIterator.php 0000644 00000001150 15120025741 0016101 0 ustar 00 <?php namespace Doctrine\DBAL\Driver; use IteratorAggregate; use ReturnTypeWillChange; /** * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn(). */ class StatementIterator implements IteratorAggregate { /** @var ResultStatement */ private $statement; public function __construct(ResultStatement $statement) { $this->statement = $statement; } /** * {@inheritdoc} */ #[ReturnTypeWillChange] public function getIterator() { while (($result = $this->statement->fetch()) !== false) { yield $result; } } } dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php 0000644 00000002566 15120025741 0017532 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; /** * MySQL Session Init Event Subscriber which allows to set the Client Encoding of the Connection. * * @deprecated Use "charset" option to PDO MySQL Connection instead. */ class MysqlSessionInit implements EventSubscriber { /** * The charset. * * @var string */ private $charset; /** * The collation, or FALSE if no collation. * * @var string|bool */ private $collation; /** * Configure Charset and Collation options of MySQL Client for each Connection. * * @param string $charset The charset. * @param string|bool $collation The collation, or FALSE if no collation. */ public function __construct($charset = 'utf8', $collation = false) { $this->charset = $charset; $this->collation = $collation; } /** * @return void */ public function postConnect(ConnectionEventArgs $args) { $collation = $this->collation ? ' COLLATE ' . $this->collation : ''; $args->getConnection()->executeStatement('SET NAMES ' . $this->charset . $collation); } /** * {@inheritdoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php 0000644 00000004014 15120025741 0017620 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use function array_change_key_case; use function array_merge; use function count; use function implode; use const CASE_UPPER; /** * Should be used when Oracle Server default environment does not match the Doctrine requirements. * * The following environment variables are required for the Doctrine default date format: * * NLS_TIME_FORMAT="HH24:MI:SS" * NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" */ class OracleSessionInit implements EventSubscriber { /** @var string[] */ protected $_defaultSessionVars = [ 'NLS_TIME_FORMAT' => 'HH24:MI:SS', 'NLS_DATE_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', 'NLS_TIMESTAMP_FORMAT' => 'YYYY-MM-DD HH24:MI:SS', 'NLS_TIMESTAMP_TZ_FORMAT' => 'YYYY-MM-DD HH24:MI:SS TZH:TZM', 'NLS_NUMERIC_CHARACTERS' => '.,', ]; /** * @param string[] $oracleSessionVars */ public function __construct(array $oracleSessionVars = []) { $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); } /** * @return void */ public function postConnect(ConnectionEventArgs $args) { if (! count($this->_defaultSessionVars)) { return; } $vars = []; foreach (array_change_key_case($this->_defaultSessionVars, CASE_UPPER) as $option => $value) { if ($option === 'CURRENT_SCHEMA') { $vars[] = $option . ' = ' . $value; } else { $vars[] = $option . " = '" . $value . "'"; } } $sql = 'ALTER SESSION SET ' . implode(' ', $vars); $args->getConnection()->executeStatement($sql); } /** * {@inheritdoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php 0000644 00000001426 15120025741 0017056 0 ustar 00 <?php namespace Doctrine\DBAL\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; /** * Session init listener for executing a single SQL statement right after a connection is opened. */ class SQLSessionInit implements EventSubscriber { /** @var string */ protected $sql; /** * @param string $sql */ public function __construct($sql) { $this->sql = $sql; } /** * @return void */ public function postConnect(ConnectionEventArgs $args) { $conn = $args->getConnection(); $conn->exec($this->sql); } /** * {@inheritdoc} */ public function getSubscribedEvents() { return [Events::postConnect]; } } dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php 0000644 00000004425 15120025741 0016177 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\Deprecations\Deprecation; /** * Event Arguments used when a Driver connection is established inside {@link Connection}. */ class ConnectionEventArgs extends EventArgs { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** * @return Connection */ public function getConnection() { return $this->connection; } /** * @deprecated Use ConnectionEventArgs::getConnection() and Connection::getDriver() instead. * * @return Driver */ public function getDriver() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'ConnectionEventArgs::getDriver() is deprecated, ' . 'use ConnectionEventArgs::getConnection()->getDriver() instead.' ); return $this->connection->getDriver(); } /** * @deprecated Use ConnectionEventArgs::getConnection() and Connection::getDatabasePlatform() instead. * * @return AbstractPlatform */ public function getDatabasePlatform() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'ConnectionEventArgs::getDatabasePlatform() is deprecated, ' . 'use ConnectionEventArgs::getConnection()->getDatabasePlatform() instead.' ); return $this->connection->getDatabasePlatform(); } /** * @deprecated Use ConnectionEventArgs::getConnection() and Connection::getSchemaManager() instead. * * @return AbstractSchemaManager */ public function getSchemaManager() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'ConnectionEventArgs::getSchemaManager() is deprecated, ' . 'use ConnectionEventArgs::getConnection()->getSchemaManager() instead.' ); return $this->connection->getSchemaManager(); } } dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php 0000644 00000004134 15120025741 0021024 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\Deprecations\Deprecation; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for adding table columns are generated inside {@link AbstractPlatform}. */ class SchemaAlterTableAddColumnEventArgs extends SchemaEventArgs { /** @var Column */ private $column; /** @var TableDiff */ private $tableDiff; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** * @return Column */ public function getColumn() { return $this->column; } /** * @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableAddColumnEventArgs */ public function addSql($sql) { if (is_array($sql)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Passing multiple SQL statements as an array to SchemaAlterTableAddColumnEventaArrgs::addSql() ' . 'is deprecated. Pass each statement as an individual argument instead.' ); } $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php 0000644 00000003336 15120025741 0021524 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for changing table columns are generated inside {@link AbstractPlatform}. */ class SchemaAlterTableChangeColumnEventArgs extends SchemaEventArgs { /** @var ColumnDiff */ private $columnDiff; /** @var TableDiff */ private $tableDiff; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; public function __construct(ColumnDiff $columnDiff, TableDiff $tableDiff, AbstractPlatform $platform) { $this->columnDiff = $columnDiff; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** * @return ColumnDiff */ public function getColumnDiff() { return $this->columnDiff; } /** * @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableChangeColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php 0000644 00000002645 15120025741 0017242 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@link AbstractPlatform}. */ class SchemaAlterTableEventArgs extends SchemaEventArgs { /** @var TableDiff */ private $tableDiff; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; public function __construct(TableDiff $tableDiff, AbstractPlatform $platform) { $this->tableDiff = $tableDiff; $this->platform = $platform; } /** * @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php 0000644 00000003267 15120025741 0021577 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for removing table columns are generated inside {@link AbstractPlatform}. */ class SchemaAlterTableRemoveColumnEventArgs extends SchemaEventArgs { /** @var Column */ private $column; /** @var TableDiff */ private $tableDiff; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** * @return Column */ public function getColumn() { return $this->column; } /** * @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRemoveColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php 0000644 00000003751 15120025741 0021547 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\TableDiff; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for renaming table columns are generated inside {@link AbstractPlatform}. */ class SchemaAlterTableRenameColumnEventArgs extends SchemaEventArgs { /** @var string */ private $oldColumnName; /** @var Column */ private $column; /** @var TableDiff */ private $tableDiff; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; /** * @param string $oldColumnName */ public function __construct($oldColumnName, Column $column, TableDiff $tableDiff, AbstractPlatform $platform) { $this->oldColumnName = $oldColumnName; $this->column = $column; $this->tableDiff = $tableDiff; $this->platform = $platform; } /** * @return string */ public function getOldColumnName() { return $this->oldColumnName; } /** * @return Column */ public function getColumn() { return $this->column; } /** * @return TableDiff */ public function getTableDiff() { return $this->tableDiff; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaAlterTableRenameColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php 0000644 00000005003 15120025741 0020460 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\Deprecations\Deprecation; /** * Event Arguments used when the portable column definition is generated inside {@link AbstractPlatform}. */ class SchemaColumnDefinitionEventArgs extends SchemaEventArgs { /** @var Column|null */ private $column; /** * Raw column data as fetched from the database. * * @var mixed[] */ private $tableColumn; /** @var string */ private $table; /** @var string */ private $database; /** @var Connection */ private $connection; /** * @param mixed[] $tableColumn * @param string $table * @param string $database */ public function __construct(array $tableColumn, $table, $database, Connection $connection) { $this->tableColumn = $tableColumn; $this->table = $table; $this->database = $database; $this->connection = $connection; } /** * Allows to clear the column which means the column will be excluded from * tables column list. * * @return SchemaColumnDefinitionEventArgs */ public function setColumn(?Column $column = null) { $this->column = $column; return $this; } /** * @return Column|null */ public function getColumn() { return $this->column; } /** * @return mixed[] */ public function getTableColumn() { return $this->tableColumn; } /** * @return string */ public function getTable() { return $this->table; } /** * @return string */ public function getDatabase() { return $this->database; } /** * @return Connection */ public function getConnection() { return $this->connection; } /** * @deprecated Use SchemaColumnDefinitionEventArgs::getConnection() and Connection::getDatabasePlatform() instead. * * @return AbstractPlatform */ public function getDatabasePlatform() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'SchemaColumnDefinitionEventArgs::getDatabasePlatform() is deprecated, ' . 'use SchemaColumnDefinitionEventArgs::getConnection()->getDatabasePlatform() instead.' ); return $this->connection->getDatabasePlatform(); } } dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php 0000644 00000003206 15120025741 0020546 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating table columns are generated inside {@link AbstractPlatform}. */ class SchemaCreateTableColumnEventArgs extends SchemaEventArgs { /** @var Column */ private $column; /** @var Table */ private $table; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; public function __construct(Column $column, Table $table, AbstractPlatform $platform) { $this->column = $column; $this->table = $table; $this->platform = $platform; } /** * @return Column */ public function getColumn() { return $this->column; } /** * @return Table */ public function getTable() { return $this->table; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableColumnEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php 0000644 00000003605 15120025741 0017373 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use function array_merge; use function func_get_args; use function is_array; /** * Event Arguments used when SQL queries for creating tables are generated inside {@link AbstractPlatform}. */ class SchemaCreateTableEventArgs extends SchemaEventArgs { /** @var Table */ private $table; /** @var mixed[][] */ private $columns; /** @var mixed[] */ private $options; /** @var AbstractPlatform */ private $platform; /** @var string[] */ private $sql = []; /** * @param mixed[][] $columns * @param mixed[] $options */ public function __construct(Table $table, array $columns, array $options, AbstractPlatform $platform) { $this->table = $table; $this->columns = $columns; $this->options = $options; $this->platform = $platform; } /** * @return Table */ public function getTable() { return $this->table; } /** * @return mixed[][] */ public function getColumns() { return $this->columns; } /** * @return mixed[] */ public function getOptions() { return $this->options; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * Passing multiple SQL statements as an array is deprecated. Pass each statement as an individual argument instead. * * @param string|string[] $sql * * @return SchemaCreateTableEventArgs */ public function addSql($sql) { $this->sql = array_merge($this->sql, is_array($sql) ? $sql : func_get_args()); return $this; } /** * @return string[] */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php 0000644 00000002364 15120025741 0017075 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Table; use InvalidArgumentException; /** * Event Arguments used when the SQL query for dropping tables are generated inside {@link AbstractPlatform}. */ class SchemaDropTableEventArgs extends SchemaEventArgs { /** @var string|Table */ private $table; /** @var AbstractPlatform */ private $platform; /** @var string|null */ private $sql; /** * @param string|Table $table * * @throws InvalidArgumentException */ public function __construct($table, AbstractPlatform $platform) { $this->table = $table; $this->platform = $platform; } /** * @return string|Table */ public function getTable() { return $this->table; } /** * @return AbstractPlatform */ public function getPlatform() { return $this->platform; } /** * @param string $sql * * @return SchemaDropTableEventArgs */ public function setSql($sql) { $this->sql = $sql; return $this; } /** * @return string|null */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php 0000644 00000000774 15120025741 0015303 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs; /** * Base class for schema related events. */ class SchemaEventArgs extends EventArgs { /** @var bool */ private $preventDefault = false; /** * @return SchemaEventArgs */ public function preventDefault() { $this->preventDefault = true; return $this; } /** * @return bool */ public function isDefaultPrevented() { return $this->preventDefault; } } dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php 0000644 00000004354 15120025741 0020302 0 ustar 00 <?php namespace Doctrine\DBAL\Event; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Index; use Doctrine\Deprecations\Deprecation; /** * Event Arguments used when the portable index definition is generated inside {@link AbstractSchemaManager}. */ class SchemaIndexDefinitionEventArgs extends SchemaEventArgs { /** @var Index|null */ private $index; /** * Raw index data as fetched from the database. * * @var mixed[] */ private $tableIndex; /** @var string */ private $table; /** @var Connection */ private $connection; /** * @param mixed[] $tableIndex * @param string $table */ public function __construct(array $tableIndex, $table, Connection $connection) { $this->tableIndex = $tableIndex; $this->table = $table; $this->connection = $connection; } /** * Allows to clear the index which means the index will be excluded from tables index list. * * @return SchemaIndexDefinitionEventArgs */ public function setIndex(?Index $index = null) { $this->index = $index; return $this; } /** * @return Index|null */ public function getIndex() { return $this->index; } /** * @return mixed[] */ public function getTableIndex() { return $this->tableIndex; } /** * @return string */ public function getTable() { return $this->table; } /** * @return Connection */ public function getConnection() { return $this->connection; } /** * @deprecated Use SchemaIndexDefinitionEventArgs::getConnection() and Connection::getDatabasePlatform() instead. * * @return AbstractPlatform */ public function getDatabasePlatform() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'SchemaIndexDefinitionEventArgs::getDatabasePlatform() is deprecated, ' . 'use SchemaIndexDefinitionEventArgs::getConnection()->getDatabasePlatform() instead.' ); return $this->connection->getDatabasePlatform(); } } dbal/lib/Doctrine/DBAL/Exception/ConnectionException.php 0000644 00000000310 15120025741 0017101 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all connection related errors detected in the driver. * * @psalm-immutable */ class ConnectionException extends DriverException { } dbal/lib/Doctrine/DBAL/Exception/ConnectionLost.php 0000644 00000000202 15120025741 0016064 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * @psalm-immutable */ final class ConnectionLost extends ConnectionException { } dbal/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php 0000644 00000000333 15120025741 0021020 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all constraint violation related errors detected in the driver. * * @psalm-immutable */ class ConstraintViolationException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectExistsException.php 0000644 00000000656 15120025741 0021052 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all already existing database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectExistsException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectNotFoundException.php 0000644 00000000647 15120025741 0021327 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all unknown database object related errors detected in the driver. * * A database object is considered any asset that can be created in a database * such as schemas, tables, views, sequences, triggers, constraints, indexes, * functions, stored procedures etc. * * @psalm-immutable */ class DatabaseObjectNotFoundException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/DeadlockException.php 0000644 00000000347 15120025741 0016522 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a deadlock error of a transaction detected in the driver. * * @psalm-immutable */ class DeadlockException extends ServerException implements RetryableException { } dbal/lib/Doctrine/DBAL/Exception/DriverException.php 0000644 00000002562 15120025741 0016250 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Exception; /** * Base class for all errors detected in the driver. * * @psalm-immutable */ class DriverException extends Exception { /** * The previous DBAL driver exception. * * @var DeprecatedDriverException */ private $driverException; /** * @param string $message The exception message. * @param DeprecatedDriverException $driverException The DBAL driver exception to chain. */ public function __construct($message, DeprecatedDriverException $driverException) { parent::__construct($message, 0, $driverException); $this->driverException = $driverException; } /** * Returns the driver specific error code if given. * * Returns null if no error code was given by the driver. * * @return int|string|null */ public function getErrorCode() { return $this->driverException->getErrorCode(); } /** * Returns the SQLSTATE the driver was in at the time the error occurred, if given. * * Returns null if no SQLSTATE was given by the driver. * * @return string|null */ public function getSQLState() { return $this->driverException->getSQLState(); } } dbal/lib/Doctrine/DBAL/Exception/ForeignKeyConstraintViolationException.php 0000644 00000000354 15120025741 0023006 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a foreign key constraint violation detected in the driver. * * @psalm-immutable */ class ForeignKeyConstraintViolationException extends ConstraintViolationException { } dbal/lib/Doctrine/DBAL/Exception/InvalidArgumentException.php 0000644 00000000631 15120025741 0020101 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; /** * Exception to be thrown when invalid arguments are passed to any DBAL API * * @psalm-immutable */ class InvalidArgumentException extends Exception { /** * @return self */ public static function fromEmptyCriteria() { return new self('Empty criteria was used, expected non-empty criteria'); } } dbal/lib/Doctrine/DBAL/Exception/InvalidFieldNameException.php 0000644 00000000336 15120025741 0020145 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an invalid specified field name in a statement detected in the driver. * * @psalm-immutable */ class InvalidFieldNameException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/LockWaitTimeoutException.php 0000644 00000000367 15120025741 0020102 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a lock wait timeout error of a transaction detected in the driver. * * @psalm-immutable */ class LockWaitTimeoutException extends ServerException implements RetryableException { } dbal/lib/Doctrine/DBAL/Exception/NoKeyValue.php 0000644 00000000730 15120025741 0015153 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception; use function sprintf; /** * @internal * * @psalm-immutable */ final class NoKeyValue extends Exception { public static function fromColumnCount(int $columnCount): self { return new self( sprintf( 'Fetching as key-value pairs requires the result to contain at least 2 columns, %d given.', $columnCount ) ); } } dbal/lib/Doctrine/DBAL/Exception/NonUniqueFieldNameException.php 0000644 00000000354 15120025741 0020500 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a non-unique/ambiguous specified field name in a statement detected in the driver. * * @psalm-immutable */ class NonUniqueFieldNameException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/NotNullConstraintViolationException.php 0000644 00000000346 15120025741 0022340 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a NOT NULL constraint violation detected in the driver. * * @psalm-immutable */ class NotNullConstraintViolationException extends ConstraintViolationException { } dbal/lib/Doctrine/DBAL/Exception/ReadOnlyException.php 0000644 00000000341 15120025741 0016523 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a write operation attempt on a read-only database element detected in the driver. * * @psalm-immutable */ class ReadOnlyException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/RetryableException.php 0000644 00000000340 15120025741 0016736 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; use Throwable; /** * Marker interface for all exceptions where retrying the transaction makes sense. * * @psalm-immutable */ interface RetryableException extends Throwable { } dbal/lib/Doctrine/DBAL/Exception/ServerException.php 0000644 00000000300 15120025741 0016247 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Base class for all server related errors detected in the driver. * * @psalm-immutable */ class ServerException extends DriverException { } dbal/lib/Doctrine/DBAL/Exception/SyntaxErrorException.php 0000644 00000000310 15120025741 0017302 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a syntax error in a statement detected in the driver. * * @psalm-immutable */ class SyntaxErrorException extends ServerException { } dbal/lib/Doctrine/DBAL/Exception/TableExistsException.php 0000644 00000000354 15120025741 0017241 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an already existing table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableExistsException extends DatabaseObjectExistsException { } dbal/lib/Doctrine/DBAL/Exception/TableNotFoundException.php 0000644 00000000347 15120025741 0017520 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for an unknown table referenced in a statement detected in the driver. * * @psalm-immutable */ class TableNotFoundException extends DatabaseObjectNotFoundException { } dbal/lib/Doctrine/DBAL/Exception/UniqueConstraintViolationException.php 0000644 00000000343 15120025741 0022210 0 ustar 00 <?php namespace Doctrine\DBAL\Exception; /** * Exception for a unique constraint violation detected in the driver. * * @psalm-immutable */ class UniqueConstraintViolationException extends ConstraintViolationException { } dbal/lib/Doctrine/DBAL/ForwardCompatibility/DriverResultStatement.php 0000644 00000000237 15120025741 0021652 0 ustar 00 <?php namespace Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL; interface DriverResultStatement extends DBAL\Driver\ResultStatement, DBAL\Result { } dbal/lib/Doctrine/DBAL/ForwardCompatibility/DriverStatement.php 0000644 00000000223 15120025741 0020446 0 ustar 00 <?php namespace Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL; interface DriverStatement extends DBAL\Driver\Statement, DBAL\Result { } dbal/lib/Doctrine/DBAL/ForwardCompatibility/Result.php 0000644 00000023227 15120025741 0016615 0 ustar 00 <?php namespace Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\NoKeyValue; use Doctrine\DBAL\ParameterType; use Doctrine\Deprecations\Deprecation; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use Traversable; use function array_shift; use function func_get_args; use function method_exists; /** * A wrapper around a Doctrine\DBAL\Driver\ResultStatement that adds 3.0 features * defined in Result interface */ class Result implements IteratorAggregate, DriverStatement, DriverResultStatement { /** @var Driver\ResultStatement */ private $stmt; public static function ensure(Driver\ResultStatement $stmt): Result { if ($stmt instanceof Result) { return $stmt; } return new Result($stmt); } public function __construct(Driver\ResultStatement $stmt) { $this->stmt = $stmt; } /** * @return Driver\ResultStatement */ #[ReturnTypeWillChange] public function getIterator() { return $this->stmt; } /** * {@inheritDoc} * * @deprecated Use Result::free() instead. */ public function closeCursor() { return $this->stmt->closeCursor(); } /** * {@inheritDoc} */ public function columnCount() { return $this->stmt->columnCount(); } /** * {@inheritDoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3); } /** * {@inheritDoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::fetch() is deprecated, use Result::fetchNumeric(), fetchAssociative() or fetchOne() instead.' ); return $this->stmt->fetch(...func_get_args()); } /** * {@inheritDoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::fetchAll() is deprecated, use Result::fetchAllNumeric(), fetchAllAssociative() or ' . 'fetchFirstColumn() instead.' ); return $this->stmt->fetchAll($fetchMode, $fetchArgument, $ctorArgs); } /** * {@inheritDoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::fetchColumn() is deprecated, use Result::fetchOne() instead.' ); return $this->stmt->fetchColumn($columnIndex); } /** * {@inheritDoc} */ public function fetchNumeric() { return $this->stmt->fetch(PDO::FETCH_NUM); } /** * {@inheritDoc} */ public function fetchAssociative() { return $this->stmt->fetch(PDO::FETCH_ASSOC); } /** * {@inheritDoc} */ public function fetchOne() { $row = $this->fetchNumeric(); if ($row === false) { return false; } return $row[0]; } /** * {@inheritDoc} */ public function fetchAllNumeric(): array { $rows = []; while (($row = $this->fetchNumeric()) !== false) { $rows[] = $row; } return $rows; } /** * {@inheritDoc} */ public function fetchAllAssociative(): array { $rows = []; while (($row = $this->fetchAssociative()) !== false) { $rows[] = $row; } return $rows; } /** * {@inheritDoc} */ public function fetchAllKeyValue(): array { $this->ensureHasKeyValue(); $data = []; foreach ($this->fetchAllNumeric() as [$key, $value]) { $data[$key] = $value; } return $data; } /** * {@inheritDoc} */ public function fetchAllAssociativeIndexed(): array { $data = []; foreach ($this->fetchAllAssociative() as $row) { $data[array_shift($row)] = $row; } return $data; } /** * {@inheritDoc} */ public function fetchFirstColumn(): array { $rows = []; while (($row = $this->fetchOne()) !== false) { $rows[] = $row; } return $rows; } /** * {@inheritdoc} * * @return Traversable<int,array<int,mixed>> */ public function iterateNumeric(): Traversable { while (($row = $this->fetchNumeric()) !== false) { yield $row; } } /** * {@inheritDoc} * * @return Traversable<int,array<string,mixed>> */ public function iterateAssociative(): Traversable { while (($row = $this->fetchAssociative()) !== false) { yield $row; } } /** * {@inheritDoc} * * @return Traversable<mixed,mixed> */ public function iterateKeyValue(): Traversable { $this->ensureHasKeyValue(); foreach ($this->iterateNumeric() as [$key, $value]) { yield $key => $value; } } /** * {@inheritDoc} * * @return Traversable<mixed,array<string,mixed>> */ public function iterateAssociativeIndexed(): Traversable { foreach ($this->iterateAssociative() as $row) { yield array_shift($row) => $row; } } /** * {@inheritDoc} * * @return Traversable<int,mixed> */ public function iterateColumn(): Traversable { while (($value = $this->fetchOne()) !== false) { yield $value; } } /** * {@inheritDoc} */ public function rowCount() { if (method_exists($this->stmt, 'rowCount')) { return $this->stmt->rowCount(); } throw Exception::notSupported('rowCount'); } public function free(): void { $this->closeCursor(); } private function ensureHasKeyValue(): void { $columnCount = $this->columnCount(); if ($columnCount < 2) { throw NoKeyValue::fromColumnCount($columnCount); } } /** * {@inheritDoc} * * @deprecated This feature will no longer be available on Result object in 3.0.x version. */ public function bindValue($param, $value, $type = ParameterType::STRING) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::bindValue() is deprecated, no replacement.' ); if ($this->stmt instanceof Driver\Statement) { return $this->stmt->bindValue($param, $value, $type); } throw Exception::notSupported('bindValue'); } /** * {@inheritDoc} * * @deprecated This feature will no longer be available on Result object in 3.0.x version. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::bindParam() is deprecated, no replacement.' ); if ($this->stmt instanceof Driver\Statement) { return $this->stmt->bindParam($param, $variable, $type, $length); } throw Exception::notSupported('bindParam'); } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::errorCode() is deprecated, the error information is available via exceptions.' ); if ($this->stmt instanceof Driver\Statement) { return $this->stmt->errorCode(); } throw Exception::notSupported('errorCode'); } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::errorInfo() is deprecated, the error information is available via exceptions.' ); if ($this->stmt instanceof Driver\Statement) { return $this->stmt->errorInfo(); } throw Exception::notSupported('errorInfo'); } /** * {@inheritDoc} * * @deprecated This feature will no longer be available on Result object in 3.0.x version. */ public function execute($params = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Result::execute() is deprecated, no replacement.' ); if ($this->stmt instanceof Driver\Statement) { return $this->stmt->execute($params); } throw Exception::notSupported('execute'); } } dbal/lib/Doctrine/DBAL/Id/TableGenerator.php 0000644 00000012176 15120025741 0014434 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\DBAL\LockMode; use Throwable; use function array_change_key_case; use function assert; use function is_int; use const CASE_LOWER; /** * Table ID Generator for those poor languages that are missing sequences. * * WARNING: The Table Id Generator clones a second independent database * connection to work correctly. This means using the generator requests that * generate IDs will have two open database connections. This is necessary to * be safe from transaction failures in the main connection. Make sure to only * ever use one TableGenerator otherwise you end up with many connections. * * TableID Generator does not work with SQLite. * * The TableGenerator does not take care of creating the SQL Table itself. You * should look at the `TableGeneratorSchemaVisitor` to do this for you. * Otherwise the schema for a table looks like: * * CREATE sequences ( * sequence_name VARCHAR(255) NOT NULL, * sequence_value INT NOT NULL DEFAULT 1, * sequence_increment_by INT NOT NULL DEFAULT 1, * PRIMARY KEY (sequence_name) * ); * * Technically this generator works as follows: * * 1. Use a robust transaction serialization level. * 2. Open transaction * 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE) * 4. Increment current value by one and write back to database * 5. Commit transaction * * If you are using a sequence_increment_by value that is larger than one the * ID Generator will keep incrementing values until it hits the incrementation * gap before issuing another query. * * If no row is present for a given sequence a new one will be created with the * default values 'value' = 1 and 'increment_by' = 1 */ class TableGenerator { /** @var Connection */ private $conn; /** @var string */ private $generatorTableName; /** @var mixed[][] */ private $sequences = []; /** * @param string $generatorTableName * * @throws Exception */ public function __construct(Connection $conn, $generatorTableName = 'sequences') { if ($conn->getDriver() instanceof Driver\PDOSqlite\Driver) { throw new Exception('Cannot use TableGenerator with SQLite.'); } $this->conn = DriverManager::getConnection( $conn->getParams(), $conn->getConfiguration(), $conn->getEventManager() ); $this->generatorTableName = $generatorTableName; } /** * Generates the next unused value for the given sequence name. * * @param string $sequence * * @return int * * @throws Exception */ public function nextValue($sequence) { if (isset($this->sequences[$sequence])) { $value = $this->sequences[$sequence]['value']; $this->sequences[$sequence]['value']++; if ($this->sequences[$sequence]['value'] >= $this->sequences[$sequence]['max']) { unset($this->sequences[$sequence]); } return $value; } $this->conn->beginTransaction(); try { $platform = $this->conn->getDatabasePlatform(); $sql = 'SELECT sequence_value, sequence_increment_by' . ' FROM ' . $platform->appendLockHint($this->generatorTableName, LockMode::PESSIMISTIC_WRITE) . ' WHERE sequence_name = ? ' . $platform->getWriteLockSQL(); $row = $this->conn->fetchAssociative($sql, [$sequence]); if ($row !== false) { $row = array_change_key_case($row, CASE_LOWER); $value = $row['sequence_value']; $value++; assert(is_int($value)); if ($row['sequence_increment_by'] > 1) { $this->sequences[$sequence] = [ 'value' => $value, 'max' => $row['sequence_value'] + $row['sequence_increment_by'], ]; } $sql = 'UPDATE ' . $this->generatorTableName . ' ' . 'SET sequence_value = sequence_value + sequence_increment_by ' . 'WHERE sequence_name = ? AND sequence_value = ?'; $rows = $this->conn->executeStatement($sql, [$sequence, $row['sequence_value']]); if ($rows !== 1) { throw new Exception('Race-condition detected while updating sequence. Aborting generation'); } } else { $this->conn->insert( $this->generatorTableName, ['sequence_name' => $sequence, 'sequence_value' => 1, 'sequence_increment_by' => 1] ); $value = 1; } $this->conn->commit(); } catch (Throwable $e) { $this->conn->rollBack(); throw new Exception( 'Error occurred while generating ID with TableGenerator, aborted generation: ' . $e->getMessage(), 0, $e ); } return $value; } } dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php 0000644 00000003002 15120025741 0017121 0 ustar 00 <?php namespace Doctrine\DBAL\Id; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; class TableGeneratorSchemaVisitor implements Visitor { /** @var string */ private $generatorTableName; /** * @param string $generatorTableName */ public function __construct($generatorTableName = 'sequences') { $this->generatorTableName = $generatorTableName; } /** * {@inheritdoc} */ public function acceptSchema(Schema $schema) { $table = $schema->createTable($this->generatorTableName); $table->addColumn('sequence_name', 'string'); $table->addColumn('sequence_value', 'integer', ['default' => 1]); $table->addColumn('sequence_increment_by', 'integer', ['default' => 1]); } /** * {@inheritdoc} */ public function acceptTable(Table $table) { } /** * {@inheritdoc} */ public function acceptColumn(Table $table, Column $column) { } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritdoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { } } dbal/lib/Doctrine/DBAL/Logging/DebugStack.php 0000644 00000002222 15120025741 0014573 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use function microtime; /** * Includes executed SQLs in a Debug Stack. */ class DebugStack implements SQLLogger { /** * Executed SQL queries. * * @var array<int, array<string, mixed>> */ public $queries = []; /** * If Debug Stack is enabled (log queries) or not. * * @var bool */ public $enabled = true; /** @var float|null */ public $start = null; /** @var int */ public $currentQuery = 0; /** * {@inheritdoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { if (! $this->enabled) { return; } $this->start = microtime(true); $this->queries[++$this->currentQuery] = [ 'sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0, ]; } /** * {@inheritdoc} */ public function stopQuery() { if (! $this->enabled) { return; } $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; } } dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php 0000644 00000001660 15120025741 0015162 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; use function var_dump; use const PHP_EOL; /** * A SQL logger that logs to the standard output using echo/var_dump. * * @deprecated */ class EchoSQLLogger implements SQLLogger { public function __construct() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3935', 'EchoSQLLogger is deprecated without replacement, move the code into your project if you rely on it.' ); } /** * {@inheritdoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { echo $sql . PHP_EOL; if ($params) { var_dump($params); } if (! $types) { return; } var_dump($types); } /** * {@inheritdoc} */ public function stopQuery() { } } dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php 0000644 00000002346 15120025741 0014750 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\Deprecations\Deprecation; /** * Chains multiple SQLLogger. */ class LoggerChain implements SQLLogger { /** @var SQLLogger[] */ private $loggers = []; /** * @param SQLLogger[] $loggers */ public function __construct(array $loggers = []) { $this->loggers = $loggers; } /** * Adds a logger in the chain. * * @deprecated Inject list of loggers via constructor instead * * @return void */ public function addLogger(SQLLogger $logger) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3572', 'LoggerChain::addLogger() is deprecated, use LoggerChain constructor instead.' ); $this->loggers[] = $logger; } /** * {@inheritdoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null) { foreach ($this->loggers as $logger) { $logger->startQuery($sql, $params, $types); } } /** * {@inheritdoc} */ public function stopQuery() { foreach ($this->loggers as $logger) { $logger->stopQuery(); } } } dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php 0000644 00000001452 15120025741 0014362 0 ustar 00 <?php namespace Doctrine\DBAL\Logging; use Doctrine\DBAL\Types\Type; /** * Interface for SQL loggers. */ interface SQLLogger { /** * Logs a SQL statement somewhere. * * @param string $sql SQL statement * @param array<int, mixed>|array<string, mixed>|null $params Statement parameters * @param array<int, Type|int|string|null>|array<string, Type|int|string|null>|null $types Parameter types * * @return void */ public function startQuery($sql, ?array $params = null, ?array $types = null); /** * Marks the last started query as stopped. This can be used for timing of queries. * * @return void */ public function stopQuery(); } dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php 0000644 00000022220 15120025741 0017046 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * DB2 Keywords. */ class DB2Keywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'DB2'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ACTIVATE', 'ADD', 'AFTER', 'ALIAS', 'ALL', 'ALLOCATE', 'DOCUMENT', 'DOUBLE', 'DROP', 'DSSIZE', 'DYNAMIC', 'EACH', 'LOCK', 'LOCKMAX', 'LOCKSIZE', 'LONG', 'LOOP', 'MAINTAINED', 'ROUND_CEILING', 'ROUND_DOWN', 'ROUND_FLOOR', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN', 'ROUND_HALF_UP', 'ALLOW', 'ALTER', 'AND', 'ANY', 'AS', 'ASENSITIVE', 'ASSOCIATE', 'ASUTIME', 'AT', 'ATTRIBUTES', 'AUDIT', 'AUTHORIZATION', 'AUX', 'AUXILIARY', 'BEFORE', 'BEGIN', 'BETWEEN', 'BINARY', 'BUFFERPOOL', 'BY', 'CACHE', 'CALL', 'CALLED', 'CAPTURE', 'CARDINALITY', 'CASCADED', 'CASE', 'CAST', 'CCSID', 'CHAR', 'CHARACTER', 'CHECK', 'CLONE', 'CLOSE', 'CLUSTER', 'COLLECTION', 'COLLID', 'COLUMN', 'COMMENT', 'COMMIT', 'CONCAT', 'CONDITION', 'CONNECT', 'CONNECTION', 'CONSTRAINT', 'CONTAINS', 'CONTINUE', 'COUNT', 'COUNT_BIG', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_LC_CTYPE', 'CURRENT_PATH', 'CURRENT_SCHEMA', 'CURRENT_SERVER', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_TIMEZONE', 'CURRENT_USER', 'CURSOR', 'CYCLE', 'DATA', 'DATABASE', 'DATAPARTITIONNAME', 'DATAPARTITIONNUM', 'EDITPROC', 'ELSE', 'ELSEIF', 'ENABLE', 'ENCODING', 'ENCRYPTION', 'END', 'END-EXEC', 'ENDING', 'ERASE', 'ESCAPE', 'EVERY', 'EXCEPT', 'EXCEPTION', 'EXCLUDING', 'EXCLUSIVE', 'EXECUTE', 'EXISTS', 'EXIT', 'EXPLAIN', 'EXTERNAL', 'EXTRACT', 'FENCED', 'FETCH', 'FIELDPROC', 'FILE', 'FINAL', 'FOR', 'FOREIGN', 'FREE', 'FROM', 'FULL', 'FUNCTION', 'GENERAL', 'GENERATED', 'GET', 'GLOBAL', 'GO', 'GOTO', 'GRANT', 'GRAPHIC', 'GROUP', 'HANDLER', 'HASH', 'HASHED_VALUE', 'HAVING', 'HINT', 'HOLD', 'HOUR', 'HOURS', 'IDENTITY', 'IF', 'IMMEDIATE', 'IN', 'INCLUDING', 'INCLUSIVE', 'INCREMENT', 'INDEX', 'INDICATOR', 'INF', 'INFINITY', 'INHERIT', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INTEGRITY', 'MATERIALIZED', 'MAXVALUE', 'MICROSECOND', 'MICROSECONDS', 'MINUTE', 'MINUTES', 'MINVALUE', 'MODE', 'MODIFIES', 'MONTH', 'MONTHS', 'NAN', 'NEW', 'NEW_TABLE', 'NEXTVAL', 'NO', 'NOCACHE', 'NOCYCLE', 'NODENAME', 'NODENUMBER', 'NOMAXVALUE', 'NOMINVALUE', 'NONE', 'NOORDER', 'NORMALIZED', 'NOT', 'NULL', 'NULLS', 'NUMPARTS', 'OBID', 'OF', 'OLD', 'OLD_TABLE', 'ON', 'OPEN', 'OPTIMIZATION', 'OPTIMIZE', 'OPTION', 'OR', 'ORDER', 'OUT', 'OUTER', 'OVER', 'OVERRIDING', 'PACKAGE', 'PADDED', 'PAGESIZE', 'PARAMETER', 'PART', 'PARTITION', 'PARTITIONED', 'PARTITIONING', 'PARTITIONS', 'PASSWORD', 'PATH', 'PIECESIZE', 'PLAN', 'POSITION', 'PRECISION', 'PREPARE', 'PREVVAL', 'PRIMARY', 'PRIQTY', 'PRIVILEGES', 'PROCEDURE', 'PROGRAM', 'PSID', 'ROUND_UP', 'ROUTINE', 'ROW', 'ROW_NUMBER', 'ROWNUMBER', 'ROWS', 'ROWSET', 'RRN', 'RUN', 'SAVEPOINT', 'SCHEMA', 'SCRATCHPAD', 'SCROLL', 'SEARCH', 'SECOND', 'SECONDS', 'SECQTY', 'SECURITY', 'SELECT', 'SENSITIVE', 'SEQUENCE', 'SESSION', 'SESSION_USER', 'SET', 'SIGNAL', 'SIMPLE', 'SNAN', 'SOME', 'SOURCE', 'SPECIFIC', 'SQL', 'SQLID', 'STACKED', 'STANDARD', 'START', 'STARTING', 'STATEMENT', 'STATIC', 'STATMENT', 'STAY', 'STOGROUP', 'STORES', 'STYLE', 'SUBSTRING', 'SUMMARY', 'SYNONYM', 'SYSFUN', 'SYSIBM', 'SYSPROC', 'SYSTEM', 'SYSTEM_USER', 'TABLE', 'TABLESPACE', 'THEN', 'TIME', 'TIMESTAMP', 'TO', 'TRANSACTION', 'TRIGGER', 'TRIM', 'TRUNCATE', 'TYPE', 'UNDO', 'UNION', 'UNIQUE', 'UNTIL', 'UPDATE', 'DATE', 'DAY', 'DAYS', 'DB2GENERAL', 'DB2GENRL', 'DB2SQL', 'DBINFO', 'DBPARTITIONNAME', 'DBPARTITIONNUM', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DEFAULTS', 'DEFINITION', 'DELETE', 'DENSE_RANK', 'DENSERANK', 'DESCRIBE', 'DESCRIPTOR', 'DETERMINISTIC', 'DIAGNOSTICS', 'DISABLE', 'DISALLOW', 'DISCONNECT', 'DISTINCT', 'DO', 'INTERSECT', 'PUBLIC', 'USAGE', 'INTO', 'QUERY', 'USER', 'IS', 'QUERYNO', 'USING', 'ISOBID', 'RANGE', 'VALIDPROC', 'ISOLATION', 'RANK', 'VALUE', 'ITERATE', 'READ', 'VALUES', 'JAR', 'READS', 'VARIABLE', 'JAVA', 'RECOVERY', 'VARIANT', 'JOIN', 'REFERENCES', 'VCAT', 'KEEP', 'REFERENCING', 'VERSION', 'KEY', 'REFRESH', 'VIEW', 'LABEL', 'RELEASE', 'VOLATILE', 'LANGUAGE', 'RENAME', 'VOLUMES', 'LATERAL', 'REPEAT', 'WHEN', 'LC_CTYPE', 'RESET', 'WHENEVER', 'LEAVE', 'RESIGNAL', 'WHERE', 'LEFT', 'RESTART', 'WHILE', 'LIKE', 'RESTRICT', 'WITH', 'LINKTYPE', 'RESULT', 'WITHOUT', 'LOCAL', 'RESULT_SET_LOCATOR WLM', 'LOCALDATE', 'RETURN', 'WRITE', 'LOCALE', 'RETURNS', 'XMLELEMENT', 'LOCALTIME', 'REVOKE', 'XMLEXISTS', 'LOCALTIMESTAMP RIGHT', 'XMLNAMESPACES', 'LOCATOR', 'ROLE', 'YEAR', 'LOCATORS', 'ROLLBACK', 'YEARS', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php 0000644 00000016050 15120025741 0020126 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * Drizzle Keywordlist. */ class DrizzleKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'drizzle'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ABS', 'ALL', 'ALLOCATE', 'ALTER', 'AND', 'ANY', 'ARE', 'ARRAY', 'AS', 'ASENSITIVE', 'ASYMMETRIC', 'AT', 'ATOMIC', 'AUTHORIZATION', 'AVG', 'BEGIN', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOOLEAN', 'BOTH', 'BY', 'CALL', 'CALLED', 'CARDINALITY', 'CASCADED', 'CASE', 'CAST', 'CEIL', 'CEILING', 'CHAR', 'CHARACTER', 'CHARACTER_LENGTH', 'CHAR_LENGTH', 'CHECK', 'CLOB', 'CLOSE', 'COALESCE', 'COLLATE', 'COLLECT', 'COLUMN', 'COMMIT', 'CONDITION', 'CONNECT', 'CONSTRAINT', 'CONVERT', 'CORR', 'CORRESPONDING', 'COUNT', 'COVAR_POP', 'COVAR_SAMP', 'CREATE', 'CROSS', 'CUBE', 'CUME_DIST', 'CURRENT', 'CURRENT_DATE', 'CURRENT_DEFAULT_TRANSFORM_GROUP', 'CURRENT_PATH', 'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_TRANSFORM_GROUP_FOR_TYPE', 'CURRENT_USER', 'CURSOR', 'CYCLE', 'DATE', 'DAY', 'DEALLOCATE', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELETE', 'DENSE_RANK', 'DEREF', 'DESCRIBE', 'DETERMINISTIC', 'DISCONNECT', 'DISTINCT', 'DOUBLE', 'DROP', 'DYNAMIC', 'EACH', 'ELEMENT', 'ELSE', 'END', 'ESCAPE', 'EVERY', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXISTS', 'EXP', 'EXTERNAL', 'EXTRACT', 'FALSE', 'FETCH', 'FILTER', 'FLOAT', 'FLOOR', 'FOR', 'FOREIGN', 'FREE', 'FROM', 'FULL', 'FUNCTION', 'FUSION', 'GET', 'GLOBAL', 'GRANT', 'GROUP', 'GROUPING', 'HAVING', 'HOLD', 'HOUR', 'IDENTITY', 'IN', 'INDICATOR', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INTEGER', 'INTERSECT', 'INTERSECTION', 'INTERVAL', 'INTO', 'IS', 'JOIN', 'LANGUAGE', 'LARGE', 'LATERAL', 'LEADING', 'LEFT', 'LIKE', 'LN', 'LOCAL', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOWER', 'MATCH', 'MAX', 'MEMBER', 'MERGE', 'METHOD', 'MIN', 'MINUTE', 'MOD', 'MODIFIES', 'MODULE', 'MONTH', 'MULTISET', 'NATIONAL', 'NATURAL', 'NCHAR', 'NCLOB', 'NEW', 'NO', 'NONE', 'NORMALIZE', 'NOT', 'NULL_SYM', 'NULLIF', 'NUMERIC', 'OCTET_LENGTH', 'OF', 'OLD', 'ON', 'ONLY', 'OPEN', 'OR', 'ORDER', 'OUT', 'OUTER', 'OVER', 'OVERLAPS', 'OVERLAY', 'PARAMETER', 'PARTITION', 'PERCENTILE_CONT', 'PERCENTILE_DISC', 'PERCENT_RANK', 'POSITION', 'POWER', 'PRECISION', 'PREPARE', 'PRIMARY', 'PROCEDURE', 'RANGE', 'RANK', 'READS', 'REAL', 'RECURSIVE', 'REF', 'REFERENCES', 'REFERENCING', 'REGR_AVGX', 'REGR_AVGY', 'REGR_COUNT', 'REGR_INTERCEPT', 'REGR_R2', 'REGR_SLOPE', 'REGR_SXX', 'REGR_SXY', 'REGR_SYY', 'RELEASE', 'RESULT', 'RETURN', 'RETURNS', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROLLUP', 'ROW', 'ROWS', 'ROW_NUMBER', 'SAVEPOINT', 'SCOPE', 'SCROLL', 'SEARCH', 'SECOND', 'SELECT', 'SENSITIVE', 'SESSION_USER', 'SET', 'SIMILAR', 'SMALLINT', 'SOME', 'SPECIFIC', 'SPECIFICTYPE', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SQRT', 'START', 'STATIC', 'STDDEV_POP', 'STDDEV_SAMP', 'SUBMULTISET', 'SUBSTRING', 'SUM', 'SYMMETRIC', 'SYSTEM', 'SYSTEM_USER', 'TABLE', 'TABLESAMPLE', 'THEN', 'TIME', 'TIMESTAMP', 'TIMEZONE_HOUR', 'TIMEZONE_MINUTE', 'TO', 'TRAILING', 'TRANSLATE', 'TRANSLATION', 'TREAT', 'TRIGGER', 'TRIM', 'TRUE', 'UESCAPE', 'UNION', 'UNIQUE', 'UNKNOWN', 'UNNEST', 'UPDATE', 'UPPER', 'USER', 'USING', 'VALUE', 'VALUES', 'VARCHAR', 'VARYING', 'VAR_POP', 'VAR_SAMP', 'WHEN', 'WHENEVER', 'WHERE', 'WIDTH_BUCKET', 'WINDOW', 'WITH', 'WITHIN', 'WITHOUT', 'XML', 'XMLAGG', 'XMLATTRIBUTES', 'XMLBINARY', 'XMLCOMMENT', 'XMLCONCAT', 'XMLELEMENT', 'XMLFOREST', 'XMLNAMESPACES', 'XMLPARSE', 'XMLPI', 'XMLROOT', 'XMLSERIALIZE', 'YEAR', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php 0000644 00000002131 15120025741 0017226 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_flip; use function array_map; use function strtoupper; /** * Abstract interface for a SQL reserved keyword dictionary. * * @psalm-consistent-constructor */ abstract class KeywordList { /** @var string[]|null */ private $keywords; /** * Checks if the given word is a keyword of this dialect/vendor platform. * * @param string $word * * @return bool */ public function isKeyword($word) { if ($this->keywords === null) { $this->initializeKeywords(); } return isset($this->keywords[strtoupper($word)]); } /** * @return void */ protected function initializeKeywords() { $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); } /** * Returns the list of keywords. * * @return string[] */ abstract protected function getKeywords(); /** * Returns the name of this keyword list. * * @return string */ abstract public function getName(); } dbal/lib/Doctrine/DBAL/Platforms/Keywords/MariaDb102Keywords.php 0000644 00000013731 15120025741 0020230 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * MariaDb reserved keywords list. * * @link https://mariadb.com/kb/en/the-mariadb-library/reserved-words/ */ final class MariaDb102Keywords extends MySQLKeywords { public function getName(): string { return 'MariaDb102'; } /** * {@inheritdoc} */ protected function getKeywords(): array { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXCEPT', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GENERAL', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'OVER', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'RETURNING', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php 0000644 00000000514 15120025741 0017440 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * MsSQL Keywordlist * * @deprecated Use SQLServerKeywords class instead. * * @link www.doctrine-project.com */ class MsSQLKeywords extends SQLServerKeywords { /** * {@inheritdoc} */ public function getName() { return 'MsSQL'; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php 0000644 00000013402 15120025741 0017622 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * MySQL 5.7 reserved keywords list. */ class MySQL57Keywords extends MySQLKeywords { /** * {@inheritdoc} */ public function getName() { return 'MySQL57'; } /** * {@inheritdoc} * * @link http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-7.html */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERATED', 'GET', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL80Keywords.php 0000644 00000002372 15120025741 0017622 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_merge; /** * MySQL 8.0 reserved keywords list. */ class MySQL80Keywords extends MySQL57Keywords { /** * {@inheritdoc} */ public function getName() { return 'MySQL80'; } /** * {@inheritdoc} * * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html */ protected function getKeywords() { $keywords = parent::getKeywords(); $keywords = array_merge($keywords, [ 'ADMIN', 'ARRAY', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LATERAL', 'LEAD', 'MEMBER', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', ]); return $keywords; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php 0000644 00000013306 15120025741 0017451 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * MySQL Keywordlist. */ class MySQLKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'MySQL'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONNECTION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'GENERAL', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LABEL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO_WRITE_TO_BINLOG', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PARTITION', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RAID0', 'RANGE', 'READ', 'READ_WRITE', 'READS', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'ROWS', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SONAME', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'X509', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php 0000644 00000005337 15120025741 0017716 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * Oracle Keywordlist. */ class OracleKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'Oracle'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ACCESS', 'ELSE', 'MODIFY', 'START', 'ADD', 'EXCLUSIVE', 'NOAUDIT', 'SELECT', 'ALL', 'EXISTS', 'NOCOMPRESS', 'SESSION', 'ALTER', 'FILE', 'NOT', 'SET', 'AND', 'FLOAT', 'NOTFOUND ', 'SHARE', 'ANY', 'FOR', 'NOWAIT', 'SIZE', 'ARRAYLEN', 'FROM', 'NULL', 'SMALLINT', 'AS', 'GRANT', 'NUMBER', 'SQLBUF', 'ASC', 'GROUP', 'OF', 'SUCCESSFUL', 'AUDIT', 'HAVING', 'OFFLINE ', 'SYNONYM', 'BETWEEN', 'IDENTIFIED', 'ON', 'SYSDATE', 'BY', 'IMMEDIATE', 'ONLINE', 'TABLE', 'CHAR', 'IN', 'OPTION', 'THEN', 'CHECK', 'INCREMENT', 'OR', 'TO', 'CLUSTER', 'INDEX', 'ORDER', 'TRIGGER', 'COLUMN', 'INITIAL', 'PCTFREE', 'UID', 'COMMENT', 'INSERT', 'PRIOR', 'UNION', 'COMPRESS', 'INTEGER', 'PRIVILEGES', 'UNIQUE', 'CONNECT', 'INTERSECT', 'PUBLIC', 'UPDATE', 'CREATE', 'INTO', 'RAW', 'USER', 'CURRENT', 'IS', 'RENAME', 'VALIDATE', 'DATE', 'LEVEL', 'RESOURCE', 'VALUES', 'DECIMAL', 'LIKE', 'REVOKE', 'VARCHAR', 'DEFAULT', 'LOCK', 'ROW', 'VARCHAR2', 'DELETE', 'LONG', 'ROWID', 'VIEW', 'DESC', 'MAXEXTENTS', 'ROWLABEL', 'WHENEVER', 'DISTINCT', 'MINUS', 'ROWNUM', 'WHERE', 'DROP', 'MODE', 'ROWS', 'WITH', 'RANGE', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL100Keywords.php 0000644 00000000420 15120025741 0020721 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms\Keywords; /** * PostgreSQL 10.0 reserved keywords list. */ class PostgreSQL100Keywords extends PostgreSQL94Keywords { public function getName(): string { return 'PostgreSQL100'; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php 0000644 00000005136 15120025741 0020663 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * PostgreSQL 9.1 reserved keywords list. */ class PostgreSQL91Keywords extends PostgreSQLKeywords { /** * {@inheritdoc} */ public function getName() { return 'PostgreSQL91'; } /** * {@inheritdoc} * * @link http://www.postgresql.org/docs/9.1/static/sql-keywords-appendix.html */ protected function getKeywords() { return [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'AUTHORIZATION', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'CONCURRENTLY', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_SCHEMA', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL', 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NATURAL', 'NOT', 'NOTNULL', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER', 'OUTER', 'OVER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'RIGHT', 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'SYMMETRIC', 'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'VERBOSE', 'WHEN', 'WHERE', 'WINDOW', 'WITH', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php 0000644 00000001035 15120025741 0020656 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_merge; /** * PostgreSQL 9.2 reserved keywords list. */ class PostgreSQL92Keywords extends PostgreSQL91Keywords { /** * {@inheritdoc} */ public function getName() { return 'PostgreSQL92'; } /** * {@inheritdoc} * * @link http://www.postgresql.org/docs/9.2/static/sql-keywords-appendix.html */ protected function getKeywords() { return array_merge(parent::getKeywords(), ['COLLATION']); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL94Keywords.php 0000644 00000001166 15120025741 0020665 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_diff; use function array_merge; /** * PostgreSQL 9.4 reserved keywords list. */ class PostgreSQL94Keywords extends PostgreSQL92Keywords { /** * {@inheritdoc} */ public function getName() { return 'PostgreSQL94'; } /** * {@inheritdoc} * * @link http://www.postgresql.org/docs/9.4/static/sql-keywords-appendix.html */ protected function getKeywords() { $parentKeywords = array_diff(parent::getKeywords(), ['OVER']); return array_merge($parentKeywords, ['LATERAL']); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php 0000644 00000004320 15120025741 0020503 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * PostgreSQL Keywordlist. */ class PostgreSQLKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'PostgreSQL'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'AS', 'ASC', 'AUTHORIZATION', 'BETWEEN', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'CONSTRAINT', 'CREATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL', 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NATURAL', 'NEW', 'NOT', 'NOTNULL', 'NULL', 'OFF', 'OFFSET', 'OLD', 'ON', 'ONLY', 'OR', 'ORDER', 'OUTER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VERBOSE', 'WHEN', 'WHERE', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php 0000644 00000005123 15120025741 0022127 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use function implode; use function str_replace; class ReservedKeywordsValidator implements Visitor { /** @var KeywordList[] */ private $keywordLists = []; /** @var string[] */ private $violations = []; /** * @param KeywordList[] $keywordLists */ public function __construct(array $keywordLists) { $this->keywordLists = $keywordLists; } /** * @return string[] */ public function getViolations() { return $this->violations; } /** * @param string $word * * @return string[] */ private function isReservedWord($word) { if ($word[0] === '`') { $word = str_replace('`', '', $word); } $keywordLists = []; foreach ($this->keywordLists as $keywordList) { if (! $keywordList->isKeyword($word)) { continue; } $keywordLists[] = $keywordList->getName(); } return $keywordLists; } /** * @param string $asset * @param string[] $violatedPlatforms * * @return void */ private function addViolation($asset, $violatedPlatforms) { if (! $violatedPlatforms) { return; } $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); } /** * {@inheritdoc} */ public function acceptColumn(Table $table, Column $column) { $this->addViolation( 'Table ' . $table->getName() . ' column ' . $column->getName(), $this->isReservedWord($column->getName()) ); } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritdoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritdoc} */ public function acceptSchema(Schema $schema) { } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { } /** * {@inheritdoc} */ public function acceptTable(Table $table) { $this->addViolation( 'Table ' . $table->getName(), $this->isReservedWord($table->getName()) ); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php 0000644 00000001334 15120025741 0021006 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_diff; use function array_merge; /** * SAP Sybase SQL Anywhere 11 reserved keywords list. */ class SQLAnywhere11Keywords extends SQLAnywhereKeywords { /** * {@inheritdoc} */ public function getName() { return 'SQLAnywhere11'; } /** * {@inheritdoc} * * @link http://dcx.sybase.com/1100/en/dbreference_en11/alhakeywords.html */ protected function getKeywords() { return array_merge( array_diff( parent::getKeywords(), ['IQ'] ), [ 'MERGE', 'OPENSTRING', ] ); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php 0000644 00000001750 15120025741 0021011 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_diff; use function array_merge; /** * SAP Sybase SQL Anywhere 12 reserved keywords list. */ class SQLAnywhere12Keywords extends SQLAnywhere11Keywords { /** * {@inheritdoc} */ public function getName() { return 'SQLAnywhere12'; } /** * {@inheritdoc} * * @link http://dcx.sybase.com/1200/en/dbreference/alhakeywords.html */ protected function getKeywords() { return array_merge( array_diff( parent::getKeywords(), [ 'INDEX_LPAREN', 'SYNTAX_ERROR', 'WITH_CUBE', 'WITH_LPAREN', 'WITH_ROLLUP', ] ), [ 'DATETIMEOFFSET', 'LIMIT', 'OPENXML', 'SPATIAL', 'TREAT', ] ); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php 0000644 00000001352 15120025741 0021013 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_merge; /** * SAP Sybase SQL Anywhere 16 reserved keywords list. */ class SQLAnywhere16Keywords extends SQLAnywhere12Keywords { /** * {@inheritdoc} */ public function getName() { return 'SQLAnywhere16'; } /** * {@inheritdoc} * * @link http://dcx.sybase.com/index.html#sa160/en/dbreference/alhakeywords.html */ protected function getKeywords() { return array_merge( parent::getKeywords(), [ 'ARRAY', 'JSON', 'ROW', 'ROWTYPE', 'UNNEST', 'VARRAY', ] ); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php 0000644 00000013116 15120025741 0020645 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * SAP Sybase SQL Anywhere 10 reserved keywords list. */ class SQLAnywhereKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'SQLAnywhere'; } /** * {@inheritdoc} * * @link http://infocenter.sybase.com/help/topic/com.sybase.dbrfen10/pdf/dbrfen10.pdf?noframes=true */ protected function getKeywords() { return [ 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC', 'ATTACH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BIGINT', 'BINARY', 'BIT', 'BOTTOM', 'BREAK', 'BY', 'CALL', 'CAPABILITY', 'CASCADE', 'CASE', 'CAST', 'CHAR', 'CHAR_CONVERT', 'CHARACTER', 'CHECK', 'CHECKPOINT', 'CLOSE', 'COMMENT', 'COMMIT', 'COMPRESSED', 'CONFLICT', 'CONNECT', 'CONSTRAINT', 'CONTAINS', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CUBE', 'CURRENT', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATE', 'DBSPACE', 'DEALLOCATE', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELETE', 'DELETING', 'DESC', 'DETACH', 'DISTINCT', 'DO', 'DOUBLE', 'DROP', 'DYNAMIC', 'ELSE', 'ELSEIF', 'ENCRYPTED', 'END', 'ENDIF', 'ESCAPE', 'EXCEPT', 'EXCEPTION', 'EXEC', 'EXECUTE', 'EXISTING', 'EXISTS', 'EXTERNLOGIN', 'FETCH', 'FIRST', 'FLOAT', 'FOR', 'FORCE', 'FOREIGN', 'FORWARD', 'FROM', 'FULL', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HOLDLOCK', 'IDENTIFIED', 'IF', 'IN', 'INDEX', 'INDEX_LPAREN', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INSERTING', 'INSTALL', 'INSTEAD', 'INT', 'INTEGER', 'INTEGRATED', 'INTERSECT', 'INTO', 'IQ', 'IS', 'ISOLATION', 'JOIN', 'KERBEROS', 'KEY', 'LATERAL', 'LEFT', 'LIKE', 'LOCK', 'LOGIN', 'LONG', 'MATCH', 'MEMBERSHIP', 'MESSAGE', 'MODE', 'MODIFY', 'NATURAL', 'NCHAR', 'NEW', 'NO', 'NOHOLDLOCK', 'NOT', 'NOTIFY', 'NULL', 'NUMERIC', 'NVARCHAR', 'OF', 'OFF', 'ON', 'OPEN', 'OPTION', 'OPTIONS', 'OR', 'ORDER', 'OTHERS', 'OUT', 'OUTER', 'OVER', 'PASSTHROUGH', 'PRECISION', 'PREPARE', 'PRIMARY', 'PRINT', 'PRIVILEGES', 'PROC', 'PROCEDURE', 'PUBLICATION', 'RAISERROR', 'READTEXT', 'REAL', 'REFERENCE', 'REFERENCES', 'REFRESH', 'RELEASE', 'REMOTE', 'REMOVE', 'RENAME', 'REORGANIZE', 'RESOURCE', 'RESTORE', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROLLUP', 'SAVE', 'SAVEPOINT', 'SCROLL', 'SELECT', 'SENSITIVE', 'SESSION', 'SET', 'SETUSER', 'SHARE', 'SMALLINT', 'SOME', 'SQLCODE', 'SQLSTATE', 'START', 'STOP', 'SUBTRANS', 'SUBTRANSACTION', 'SYNCHRONIZE', 'SYNTAX_ERROR', 'TABLE', 'TEMPORARY', 'THEN', 'TIME', 'TIMESTAMP', 'TINYINT', 'TO', 'TOP', 'TRAN', 'TRIGGER', 'TRUNCATE', 'TSEQUAL', 'UNBOUNDED', 'UNION', 'UNIQUE', 'UNIQUEIDENTIFIER', 'UNKNOWN', 'UNSIGNED', 'UPDATE', 'UPDATING', 'USER', 'USING', 'VALIDATE', 'VALUES', 'VARBINARY', 'VARBIT', 'VARCHAR', 'VARIABLE', 'VARYING', 'VIEW', 'WAIT', 'WAITFOR', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WITH_CUBE', 'WITH_LPAREN', 'WITH_ROLLUP', 'WITHIN', 'WORK', 'WRITETEXT', 'XML', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php 0000644 00000001423 15120025741 0020636 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_diff; use function array_merge; /** * Microsoft SQL Server 2005 reserved keyword dictionary. * * @link www.doctrine-project.com */ class SQLServer2005Keywords extends SQLServerKeywords { /** * {@inheritdoc} */ public function getName() { return 'SQLServer2005'; } /** * {@inheritdoc} * * @link http://msdn.microsoft.com/en-US/library/ms189822%28v=sql.90%29.aspx */ protected function getKeywords() { return array_merge(array_diff(parent::getKeywords(), ['DUMMY']), [ 'EXTERNAL', 'PIVOT', 'REVERT', 'SECURITYAUDIT', 'TABLESAMPLE', 'UNPIVOT', ]); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php 0000644 00000001124 15120025741 0020637 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_merge; /** * Microsoft SQL Server 2008 reserved keyword dictionary. * * @link www.doctrine-project.com */ class SQLServer2008Keywords extends SQLServer2005Keywords { /** * {@inheritdoc} */ public function getName() { return 'SQLServer2008'; } /** * {@inheritdoc} * * @link http://msdn.microsoft.com/en-us/library/ms189822%28v=sql.100%29.aspx */ protected function getKeywords() { return array_merge(parent::getKeywords(), ['MERGE']); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php 0000644 00000001371 15120025741 0020636 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; use function array_merge; /** * Microsoft SQL Server 2012 reserved keyword dictionary. * * @link www.doctrine-project.com */ class SQLServer2012Keywords extends SQLServer2008Keywords { /** * {@inheritdoc} */ public function getName() { return 'SQLServer2012'; } /** * {@inheritdoc} * * @link http://msdn.microsoft.com/en-us/library/ms189822.aspx */ protected function getKeywords() { return array_merge(parent::getKeywords(), [ 'SEMANTICKEYPHRASETABLE', 'SEMANTICSIMILARITYDETAILSTABLE', 'SEMANTICSIMILARITYTABLE', 'TRY_CONVERT', 'WITHIN GROUP', ]); } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php 0000644 00000010654 15120025741 0020335 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * Microsoft SQL Server 2000 reserved keyword dictionary. * * @link www.doctrine-project.com */ class SQLServerKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'SQLServer'; } /** * {@inheritdoc} * * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx */ protected function getKeywords() { return [ 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC', 'AUTHORIZATION', 'BACKUP', 'BEGIN', 'BETWEEN', 'BREAK', 'BROWSE', 'BULK', 'BY', 'CASCADE', 'CASE', 'CHECK', 'CHECKPOINT', 'CLOSE', 'CLUSTERED', 'COALESCE', 'COLLATE', 'COLUMN', 'COMMIT', 'COMPUTE', 'CONSTRAINT', 'CONTAINS', 'CONTAINSTABLE', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DBCC', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DELETE', 'DENY', 'DESC', 'DISK', 'DISTINCT', 'DISTRIBUTED', 'DOUBLE', 'DROP', 'DUMP', 'ELSE', 'END', 'ERRLVL', 'ESCAPE', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXISTS', 'EXIT', 'EXTERNAL', 'FETCH', 'FILE', 'FILLFACTOR', 'FOR', 'FOREIGN', 'FREETEXT', 'FREETEXTTABLE', 'FROM', 'FULL', 'FUNCTION', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HOLDLOCK', 'IDENTITY', 'IDENTITY_INSERT', 'IDENTITYCOL', 'IF', 'IN', 'INDEX', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'KEY', 'KILL', 'LEFT', 'LIKE', 'LINENO', 'LOAD', 'NATIONAL', 'NOCHECK ', 'NONCLUSTERED', 'NOT', 'NULL', 'NULLIF', 'OF', 'OFF', 'OFFSETS', 'ON', 'OPEN', 'OPENDATASOURCE', 'OPENQUERY', 'OPENROWSET', 'OPENXML', 'OPTION', 'OR', 'ORDER', 'OUTER', 'OVER', 'PERCENT', 'PIVOT', 'PLAN', 'PRECISION', 'PRIMARY', 'PRINT', 'PROC', 'PROCEDURE', 'PUBLIC', 'RAISERROR', 'READ', 'READTEXT', 'RECONFIGURE', 'REFERENCES', 'REPLICATION', 'RESTORE', 'RESTRICT', 'RETURN', 'REVERT', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROWCOUNT', 'ROWGUIDCOL', 'RULE', 'SAVE', 'SCHEMA', 'SECURITYAUDIT', 'SELECT', 'SESSION_USER', 'SET', 'SETUSER', 'SHUTDOWN', 'SOME', 'STATISTICS', 'SYSTEM_USER', 'TABLE', 'TABLESAMPLE', 'TEXTSIZE', 'THEN', 'TO', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'TSEQUAL', 'UNION', 'UNIQUE', 'UNPIVOT', 'UPDATE', 'UPDATETEXT', 'USE', 'USER', 'VALUES', 'VARYING', 'VIEW', 'WAITFOR', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITETEXT', ]; } } dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php 0000644 00000005635 15120025741 0017653 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms\Keywords; /** * SQLite Keywordlist. */ class SQLiteKeywords extends KeywordList { /** * {@inheritdoc} */ public function getName() { return 'SQLite'; } /** * {@inheritdoc} */ protected function getKeywords() { return [ 'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BY', 'CASCADE', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', 'CONFLICT', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATABASE', 'DEFAULT', 'DEFERRABLE', 'DEFERRED', 'DELETE', 'DESC', 'DETACH', 'DISTINCT', 'DROP', 'EACH', 'ELSE', 'END', 'ESCAPE', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXPLAIN', 'FAIL', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'GLOB', 'GROUP', 'HAVING', 'IF', 'IGNORE', 'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INITIALLY', 'INNER', 'INSERT', 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'KEY', 'LEFT', 'LIKE', 'LIMIT', 'MATCH', 'NATURAL', 'NO', 'NOT', 'NOTNULL', 'NULL', 'OF', 'OFFSET', 'ON', 'OR', 'ORDER', 'OUTER', 'PLAN', 'PRAGMA', 'PRIMARY', 'QUERY', 'RAISE', 'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPLACE', 'RESTRICT', 'RIGHT', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SELECT', 'SET', 'TABLE', 'TEMP', 'TEMPORARY', 'THEN', 'TO', 'TRANSACTION', 'TRIGGER', 'UNION', 'UNIQUE', 'UPDATE', 'USING', 'VACUUM', 'VALUES', 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE', ]; } } dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php 0000644 00000314450 15120025741 0016421 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\Common\EventManager; use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; use Doctrine\DBAL\Event\SchemaDropTableEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\Keywords\KeywordList; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use UnexpectedValueException; use function addcslashes; use function array_map; use function array_merge; use function array_unique; use function array_values; use function assert; use function count; use function explode; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function in_array; use function is_array; use function is_bool; use function is_int; use function is_string; use function preg_quote; use function preg_replace; use function sprintf; use function str_replace; use function strlen; use function strpos; use function strtolower; use function strtoupper; /** * Base class for all DatabasePlatforms. The DatabasePlatforms are the central * point of abstraction of platform-specific behaviors, features and SQL dialects. * They are a passive source of information. * * @todo Remove any unnecessary methods. */ abstract class AbstractPlatform { public const CREATE_INDEXES = 1; public const CREATE_FOREIGNKEYS = 2; /** * @deprecated Use DateIntervalUnit::INTERVAL_UNIT_SECOND. */ public const DATE_INTERVAL_UNIT_SECOND = DateIntervalUnit::SECOND; /** * @deprecated Use DateIntervalUnit::MINUTE. */ public const DATE_INTERVAL_UNIT_MINUTE = DateIntervalUnit::MINUTE; /** * @deprecated Use DateIntervalUnit::HOUR. */ public const DATE_INTERVAL_UNIT_HOUR = DateIntervalUnit::HOUR; /** * @deprecated Use DateIntervalUnit::DAY. */ public const DATE_INTERVAL_UNIT_DAY = DateIntervalUnit::DAY; /** * @deprecated Use DateIntervalUnit::WEEK. */ public const DATE_INTERVAL_UNIT_WEEK = DateIntervalUnit::WEEK; /** * @deprecated Use DateIntervalUnit::MONTH. */ public const DATE_INTERVAL_UNIT_MONTH = DateIntervalUnit::MONTH; /** * @deprecated Use DateIntervalUnit::QUARTER. */ public const DATE_INTERVAL_UNIT_QUARTER = DateIntervalUnit::QUARTER; /** * @deprecated Use DateIntervalUnit::QUARTER. */ public const DATE_INTERVAL_UNIT_YEAR = DateIntervalUnit::YEAR; /** * @deprecated Use TrimMode::UNSPECIFIED. */ public const TRIM_UNSPECIFIED = TrimMode::UNSPECIFIED; /** * @deprecated Use TrimMode::LEADING. */ public const TRIM_LEADING = TrimMode::LEADING; /** * @deprecated Use TrimMode::TRAILING. */ public const TRIM_TRAILING = TrimMode::TRAILING; /** * @deprecated Use TrimMode::BOTH. */ public const TRIM_BOTH = TrimMode::BOTH; /** @var string[]|null */ protected $doctrineTypeMapping; /** * Contains a list of all columns that should generate parseable column comments for type-detection * in reverse engineering scenarios. * * @var string[]|null */ protected $doctrineTypeComments; /** @var EventManager|null */ protected $_eventManager; /** * Holds the KeywordList instance for the current platform. * * @var KeywordList|null */ protected $_keywords; public function __construct() { } /** * Sets the EventManager used by the Platform. * * @return void */ public function setEventManager(EventManager $eventManager) { $this->_eventManager = $eventManager; } /** * Gets the EventManager used by the Platform. * * @return EventManager|null */ public function getEventManager() { return $this->_eventManager; } /** * Returns the SQL snippet that declares a boolean column. * * @param mixed[] $column * * @return string */ abstract public function getBooleanTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 4 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getIntegerTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares an 8 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getBigIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares a 2 byte integer column. * * @param mixed[] $column * * @return string */ abstract public function getSmallIntTypeDeclarationSQL(array $column); /** * Returns the SQL snippet that declares common properties of an integer column. * * @param mixed[] $column * * @return string */ abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column); /** * Lazy load Doctrine Type Mappings. * * @return void */ abstract protected function initializeDoctrineTypeMappings(); /** * Initializes Doctrine Type Mappings with the platform defaults * and with all additional type mappings. * * @return void */ private function initializeAllDoctrineTypeMappings() { $this->initializeDoctrineTypeMappings(); foreach (Type::getTypesMap() as $typeName => $className) { foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { $this->doctrineTypeMapping[$dbType] = $typeName; } } } /** * Returns the SQL snippet used to declare a column that can * store characters in the ASCII character set * * @param mixed[] $column */ public function getAsciiStringTypeDeclarationSQL(array $column): string { return $this->getVarcharTypeDeclarationSQL($column); } /** * Returns the SQL snippet used to declare a VARCHAR column type. * * @param mixed[] $column * * @return string */ public function getVarcharTypeDeclarationSQL(array $column) { if (! isset($column['length'])) { $column['length'] = $this->getVarcharDefaultLength(); } $fixed = $column['fixed'] ?? false; $maxLength = $fixed ? $this->getCharMaxLength() : $this->getVarcharMaxLength(); if ($column['length'] > $maxLength) { return $this->getClobTypeDeclarationSQL($column); } return $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param mixed[] $column The column definition. * * @return string */ public function getBinaryTypeDeclarationSQL(array $column) { if (! isset($column['length'])) { $column['length'] = $this->getBinaryDefaultLength(); } $fixed = $column['fixed'] ?? false; $maxLength = $this->getBinaryMaxLength(); if ($column['length'] > $maxLength) { if ($maxLength > 0) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3187', 'Binary column length %d is greater than supported by the platform (%d).' . ' Reduce the column length or use a BLOB column instead.', $column['length'], $maxLength ); } return $this->getBlobTypeDeclarationSQL($column); } return $this->getBinaryTypeDeclarationSQLSnippet($column['length'], $fixed); } /** * Returns the SQL snippet to declare a GUID/UUID column. * * By default this maps directly to a CHAR(36) and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getGuidTypeDeclarationSQL(array $column) { $column['length'] = 36; $column['fixed'] = true; return $this->getVarcharTypeDeclarationSQL($column); } /** * Returns the SQL snippet to declare a JSON column. * * By default this maps directly to a CLOB and only maps to more * special datatypes when the underlying databases support this datatype. * * @param mixed[] $column * * @return string */ public function getJsonTypeDeclarationSQL(array $column) { return $this->getClobTypeDeclarationSQL($column); } /** * @param int $length * @param bool $fixed * * @return string * * @throws Exception If not supported on this platform. */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { throw Exception::notSupported('VARCHARs not supported by Platform.'); } /** * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. * * @param int $length The length of the column. * @param bool $fixed Whether the column length is fixed. * * @return string * * @throws Exception If not supported on this platform. */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { throw Exception::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); } /** * Returns the SQL snippet used to declare a CLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getClobTypeDeclarationSQL(array $column); /** * Returns the SQL Snippet used to declare a BLOB column type. * * @param mixed[] $column * * @return string */ abstract public function getBlobTypeDeclarationSQL(array $column); /** * Gets the name of the platform. * * @return string */ abstract public function getName(); /** * Registers a doctrine type to be used in conjunction with a column type of this platform. * * @param string $dbType * @param string $doctrineType * * @return void * * @throws Exception If the type is not found. */ public function registerDoctrineTypeMapping($dbType, $doctrineType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } if (! Types\Type::hasType($doctrineType)) { throw Exception::typeNotFound($doctrineType); } $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $doctrineType; $doctrineType = Type::getType($doctrineType); if (! $doctrineType->requiresSQLCommentHint($this)) { return; } $this->markDoctrineTypeCommented($doctrineType); } /** * Gets the Doctrine type that is mapped for the given database column type. * * @param string $dbType * * @return string * * @throws Exception */ public function getDoctrineTypeMapping($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); if (! isset($this->doctrineTypeMapping[$dbType])) { throw new Exception( 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.' ); } return $this->doctrineTypeMapping[$dbType]; } /** * Checks if a database type is currently supported by this platform. * * @param string $dbType * * @return bool */ public function hasDoctrineTypeMappingFor($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeAllDoctrineTypeMappings(); } $dbType = strtolower($dbType); return isset($this->doctrineTypeMapping[$dbType]); } /** * Initializes the Doctrine Type comments instance variable for in_array() checks. * * @return void */ protected function initializeCommentedDoctrineTypes() { $this->doctrineTypeComments = []; foreach (Type::getTypesMap() as $typeName => $className) { $type = Type::getType($typeName); if (! $type->requiresSQLCommentHint($this)) { continue; } $this->doctrineTypeComments[] = $typeName; } } /** * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? * * @return bool */ public function isCommentedDoctrineType(Type $doctrineType) { if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } assert(is_array($this->doctrineTypeComments)); return in_array($doctrineType->getName(), $this->doctrineTypeComments); } /** * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. * * @param string|Type $doctrineType * * @return void */ public function markDoctrineTypeCommented($doctrineType) { if ($this->doctrineTypeComments === null) { $this->initializeCommentedDoctrineTypes(); } assert(is_array($this->doctrineTypeComments)); $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; } /** * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. * * @return string */ public function getDoctrineTypeComment(Type $doctrineType) { return '(DC2Type:' . $doctrineType->getName() . ')'; } /** * Gets the comment of a passed column modified by potential doctrine type comment hints. * * @return string|null */ protected function getColumnComment(Column $column) { $comment = $column->getComment(); if ($this->isCommentedDoctrineType($column->getType())) { $comment .= $this->getDoctrineTypeComment($column->getType()); } return $comment; } /** * Gets the character used for identifier quoting. * * @return string */ public function getIdentifierQuoteCharacter() { return '"'; } /** * Gets the string portion that starts an SQL comment. * * @return string */ public function getSqlCommentStartString() { return '--'; } /** * Gets the string portion that ends an SQL comment. * * @return string */ public function getSqlCommentEndString() { return "\n"; } /** * Gets the maximum length of a char column. */ public function getCharMaxLength(): int { return $this->getVarcharMaxLength(); } /** * Gets the maximum length of a varchar column. * * @return int */ public function getVarcharMaxLength() { return 4000; } /** * Gets the default length of a varchar column. * * @return int */ public function getVarcharDefaultLength() { return 255; } /** * Gets the maximum length of a binary column. * * @return int */ public function getBinaryMaxLength() { return 4000; } /** * Gets the default length of a binary column. * * @return int */ public function getBinaryDefaultLength() { return 255; } /** * Gets all SQL wildcard characters of the platform. * * @return string[] */ public function getWildcards() { return ['%', '_']; } /** * Returns the regular expression operator. * * @return string * * @throws Exception If not supported on this platform. */ public function getRegexpExpression() { throw Exception::notSupported(__METHOD__); } /** * Returns the global unique identifier expression. * * @deprecated Use application-generated UUIDs instead * * @return string * * @throws Exception If not supported on this platform. */ public function getGuidExpression() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the average value of a column. * * @param string $column The column to use. * * @return string Generated SQL including an AVG aggregate function. */ public function getAvgExpression($column) { return 'AVG(' . $column . ')'; } /** * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. * * If a '*' is used instead of a column the number of selected rows is returned. * * @param string|int $column The column to use. * * @return string Generated SQL including a COUNT aggregate function. */ public function getCountExpression($column) { return 'COUNT(' . $column . ')'; } /** * Returns the SQL snippet to get the highest value of a column. * * @param string $column The column to use. * * @return string Generated SQL including a MAX aggregate function. */ public function getMaxExpression($column) { return 'MAX(' . $column . ')'; } /** * Returns the SQL snippet to get the lowest value of a column. * * @param string $column The column to use. * * @return string Generated SQL including a MIN aggregate function. */ public function getMinExpression($column) { return 'MIN(' . $column . ')'; } /** * Returns the SQL snippet to get the total sum of a column. * * @param string $column The column to use. * * @return string Generated SQL including a SUM aggregate function. */ public function getSumExpression($column) { return 'SUM(' . $column . ')'; } // scalar functions /** * Returns the SQL snippet to get the md5 sum of a column. * * Note: Not SQL92, but common functionality. * * @param string $column * * @return string */ public function getMd5Expression($column) { return 'MD5(' . $column . ')'; } /** * Returns the SQL snippet to get the length of a text column. * * @param string $column * * @return string */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ')'; } /** * Returns the SQL snippet to get the squared value of a column. * * @param string $column The column to use. * * @return string Generated SQL including an SQRT aggregate function. */ public function getSqrtExpression($column) { return 'SQRT(' . $column . ')'; } /** * Returns the SQL snippet to round a numeric column to the number of decimals specified. * * @param string $column * @param int $decimals * * @return string */ public function getRoundExpression($column, $decimals = 0) { return 'ROUND(' . $column . ', ' . $decimals . ')'; } /** * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. * * @param string $expression1 * @param string $expression2 * * @return string */ public function getModExpression($expression1, $expression2) { return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; } /** * Returns the SQL snippet to trim a string. * * @param string $str The expression to apply the trim to. * @param int $mode The position of the trim (leading/trailing/both). * @param string|bool $char The char to trim, has to be quoted already. Defaults to space. * * @return string */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $expression = ''; switch ($mode) { case TrimMode::LEADING: $expression = 'LEADING '; break; case TrimMode::TRAILING: $expression = 'TRAILING '; break; case TrimMode::BOTH: $expression = 'BOTH '; break; } if ($char !== false) { $expression .= $char . ' '; } if ($mode || $char !== false) { $expression .= 'FROM '; } return 'TRIM(' . $expression . $str . ')'; } /** * Returns the SQL snippet to trim trailing space characters from the expression. * * @param string $str Literal string or column name. * * @return string */ public function getRtrimExpression($str) { return 'RTRIM(' . $str . ')'; } /** * Returns the SQL snippet to trim leading space characters from the expression. * * @param string $str Literal string or column name. * * @return string */ public function getLtrimExpression($str) { return 'LTRIM(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to uppercase, * according to the current character set mapping. * * @param string $str Literal string or column name. * * @return string */ public function getUpperExpression($str) { return 'UPPER(' . $str . ')'; } /** * Returns the SQL snippet to change all characters from the expression to lowercase, * according to the current character set mapping. * * @param string $str Literal string or column name. * * @return string */ public function getLowerExpression($str) { return 'LOWER(' . $str . ')'; } /** * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. * * @param string $str Literal string. * @param string $substr Literal string to find. * @param int|false $startPos Position to start at, beginning of string by default. * * @return string * * @throws Exception If not supported on this platform. */ public function getLocateExpression($str, $substr, $startPos = false) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to get the current system date. * * @return string */ public function getNowExpression() { return 'NOW()'; } /** * Returns a SQL snippet to get a substring inside an SQL statement. * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function. * * @param string $string An sql string literal or column name/alias. * @param int $start Where to start the substring portion. * @param int|null $length The substring portion length. * * @return string */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * Returns a SQL snippet to concatenate the given expressions. * * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. * * @return string */ public function getConcatExpression() { return implode(' || ', func_get_args()); } /** * Returns the SQL for a logical not. * * Example: * <code> * $q = new Doctrine_Query(); * $e = $q->expr; * $q->select('*')->from('table') * ->where($e->eq('id', $e->not('null')); * </code> * * @param string $expression * * @return string The logical expression. */ public function getNotExpression($expression) { return 'NOT(' . $expression . ')'; } /** * Returns the SQL that checks if an expression is null. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNullExpression($expression) { return $expression . ' IS NULL'; } /** * Returns the SQL that checks if an expression is not null. * * @param string $expression The expression that should be compared to null. * * @return string The logical expression. */ public function getIsNotNullExpression($expression) { return $expression . ' IS NOT NULL'; } /** * Returns the SQL that checks if an expression evaluates to a value between two values. * * The parameter $expression is checked if it is between $value1 and $value2. * * Note: There is a slight difference in the way BETWEEN works on some databases. * http://www.w3schools.com/sql/sql_between.asp. If you want complete database * independence you should avoid using between(). * * @param string $expression The value to compare to. * @param string $value1 The lower value to compare with. * @param string $value2 The higher value to compare with. * * @return string The logical expression. */ public function getBetweenExpression($expression, $value1, $value2) { return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2; } /** * Returns the SQL to get the arccosine of a value. * * @param string $value * * @return string */ public function getAcosExpression($value) { return 'ACOS(' . $value . ')'; } /** * Returns the SQL to get the sine of a value. * * @param string $value * * @return string */ public function getSinExpression($value) { return 'SIN(' . $value . ')'; } /** * Returns the SQL to get the PI value. * * @return string */ public function getPiExpression() { return 'PI()'; } /** * Returns the SQL to get the cosine of a value. * * @param string $value * * @return string */ public function getCosExpression($value) { return 'COS(' . $value . ')'; } /** * Returns the SQL to calculate the difference in days between the two passed dates. * * Computes diff = date1 - date2. * * @param string $date1 * @param string $date2 * * @return string * * @throws Exception If not supported on this platform. */ public function getDateDiffExpression($date1, $date2) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to add the number of given seconds to a date. * * @param string $date * @param int $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddSecondsExpression($date, $seconds) { return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to subtract the number of given seconds from a date. * * @param string $date * @param int $seconds * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubSecondsExpression($date, $seconds) { return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); } /** * Returns the SQL to add the number of given minutes to a date. * * @param string $date * @param int $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMinutesExpression($date, $minutes) { return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to subtract the number of given minutes from a date. * * @param string $date * @param int $minutes * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMinutesExpression($date, $minutes) { return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); } /** * Returns the SQL to add the number of given hours to a date. * * @param string $date * @param int $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddHourExpression($date, $hours) { return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to subtract the number of given hours to a date. * * @param string $date * @param int $hours * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubHourExpression($date, $hours) { return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); } /** * Returns the SQL to add the number of given days to a date. * * @param string $date * @param int $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddDaysExpression($date, $days) { return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to subtract the number of given days to a date. * * @param string $date * @param int $days * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubDaysExpression($date, $days) { return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); } /** * Returns the SQL to add the number of given weeks to a date. * * @param string $date * @param int $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddWeeksExpression($date, $weeks) { return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to subtract the number of given weeks from a date. * * @param string $date * @param int $weeks * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubWeeksExpression($date, $weeks) { return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); } /** * Returns the SQL to add the number of given months to a date. * * @param string $date * @param int $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddMonthExpression($date, $months) { return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to subtract the number of given months to a date. * * @param string $date * @param int $months * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubMonthExpression($date, $months) { return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); } /** * Returns the SQL to add the number of given quarters to a date. * * @param string $date * @param int $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddQuartersExpression($date, $quarters) { return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to subtract the number of given quarters from a date. * * @param string $date * @param int $quarters * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubQuartersExpression($date, $quarters) { return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); } /** * Returns the SQL to add the number of given years to a date. * * @param string $date * @param int $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateAddYearsExpression($date, $years) { return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL to subtract the number of given years from a date. * * @param string $date * @param int $years * * @return string * * @throws Exception If not supported on this platform. */ public function getDateSubYearsExpression($date, $years) { return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); } /** * Returns the SQL for a date arithmetic expression. * * @param string $date The column or literal representing a date to perform the arithmetic operation on. * @param string $operator The arithmetic operator (+ or -). * @param int $interval The interval that shall be calculated into the date. * @param string $unit The unit of the interval that shall be calculated into the date. * One of the DATE_INTERVAL_UNIT_* constants. * * @return string * * @throws Exception If not supported on this platform. */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL bit AND comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitAndComparisonExpression($value1, $value2) { return '(' . $value1 . ' & ' . $value2 . ')'; } /** * Returns the SQL bit OR comparison expression. * * @param string $value1 * @param string $value2 * * @return string */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . ' | ' . $value2 . ')'; } /** * Returns the FOR UPDATE expression. * * @return string */ public function getForUpdateSQL() { return 'FOR UPDATE'; } /** * Honors that some SQL vendors such as MsSql use table hints for locking instead of the * ANSI SQL FOR UPDATE specification. * * @param string $fromClause The FROM clause to append the hint for the given lock mode to. * @param int|null $lockMode One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will * be appended to the FROM clause. * * @return string */ public function appendLockHint($fromClause, $lockMode) { return $fromClause; } /** * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. * * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database * vendors allow to lighten this constraint up to be a real read lock. * * @return string */ public function getReadLockSQL() { return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. * * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. * * @return string */ public function getWriteLockSQL() { return $this->getForUpdateSQL(); } /** * Returns the SQL snippet to drop an existing database. * * @param string $name The name of the database that should be dropped. * * @return string */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * Returns the SQL snippet to drop an existing table. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getDropTableSQL($table) { $tableArg = $table; if ($table instanceof Table) { $table = $table->getQuotedName($this); } if (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' ); } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { $sql = $eventArgs->getSql(); if ($sql === null) { throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL'); } return $sql; } } return 'DROP TABLE ' . $table; } /** * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. * * @param Table|string $table * * @return string */ public function getDropTemporaryTableSQL($table) { return $this->getDropTableSQL($table); } /** * Returns the SQL to drop an index from a table. * * @param Index|string $index * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' ); } return 'DROP INDEX ' . $index; } /** * Returns the SQL to drop a constraint. * * @param Constraint|string $constraint * @param Table|string $table * * @return string */ public function getDropConstraintSQL($constraint, $table) { if (! $constraint instanceof Constraint) { $constraint = new Identifier($constraint); } if (! $table instanceof Table) { $table = new Identifier($table); } $constraint = $constraint->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; } /** * Returns the SQL to drop a foreign key. * * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { if (! $foreignKey instanceof ForeignKeyConstraint) { $foreignKey = new Identifier($foreignKey); } if (! $table instanceof Table) { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; } /** * Returns the SQL statement(s) to create a table with the specified name, columns and constraints * on this platform. * * @param int $createFlags * * @return string[] The sequence of SQL statements. * * @throws Exception * @throws InvalidArgumentException */ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) { if (! is_int($createFlags)) { throw new InvalidArgumentException( 'Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.' ); } if (count($table->getColumns()) === 0) { throw Exception::noColumnsSpecifiedForTable($table->getName()); } $tableName = $table->getQuotedName($this); $options = $table->getOptions(); $options['uniqueConstraints'] = []; $options['indexes'] = []; $options['primary'] = []; if (($createFlags & self::CREATE_INDEXES) > 0) { foreach ($table->getIndexes() as $index) { if ($index->isPrimary()) { $options['primary'] = $index->getQuotedColumns($this); $options['primary_index'] = $index; } else { $options['indexes'][$index->getQuotedName($this)] = $index; } } } $columnSql = []; $columns = []; foreach ($table->getColumns() as $column) { if ( $this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn) ) { $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); if ($eventArgs->isDefaultPrevented()) { continue; } } $name = $column->getQuotedName($this); $columnData = array_merge($column->toArray(), [ 'name' => $name, 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, 'comment' => $this->getColumnComment($column), ]); if ($columnData['type'] instanceof Types\StringType && $columnData['length'] === null) { $columnData['length'] = 255; } if (in_array($column->getName(), $options['primary'])) { $columnData['primary'] = true; } $columns[$name] = $columnData; } if (($createFlags & self::CREATE_FOREIGNKEYS) > 0) { $options['foreignKeys'] = []; foreach ($table->getForeignKeys() as $fkConstraint) { $options['foreignKeys'][] = $fkConstraint; } } if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); if ($eventArgs->isDefaultPrevented()) { return array_merge($eventArgs->getSql(), $columnSql); } } $sql = $this->_getCreateTableSQL($tableName, $columns, $options); if ($this->supportsCommentOnStatement()) { if ($table->hasOption('comment')) { $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); } foreach ($table->getColumns() as $column) { $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); } } return array_merge($sql, $columnSql); } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { $tableName = new Identifier($tableName); return sprintf( 'COMMENT ON TABLE %s IS %s', $tableName->getQuotedName($this), $this->quoteStringLiteral((string) $comment) ); } /** * @param string $tableName * @param string $columnName * @param string|null $comment * * @return string */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $this->quoteStringLiteral((string) $comment) ); } /** * Returns the SQL to create inline comment on a column. * * @param string $comment * * @return string * * @throws Exception If not supported on this platform. */ public function getInlineColumnCommentSQL($comment) { if (! $this->supportsInlineColumnComments()) { throw Exception::notSupported(__METHOD__); } return 'COMMENT ' . $this->quoteStringLiteral($comment); } /** * Returns the SQL used to create a table. * * @param string $name * @param mixed[][] $columns * @param mixed[] $options * * @return string[] */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $definition) { $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * @return string */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TEMPORARY TABLE'; } /** * Returns the SQL to create a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to change a sequence on this platform. * * @return string * * @throws Exception If not supported on this platform. */ public function getAlterSequenceSQL(Sequence $sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to create a constraint on a table on this platform. * * @param Table|string $table * * @return string * * @throws InvalidArgumentException */ public function getCreateConstraintSQL(Constraint $constraint, $table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; $referencesClause = ''; if ($constraint instanceof Index) { if ($constraint->isPrimary()) { $query .= ' PRIMARY KEY'; } elseif ($constraint->isUnique()) { $query .= ' UNIQUE'; } else { throw new InvalidArgumentException( 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().' ); } } elseif ($constraint instanceof ForeignKeyConstraint) { $query .= ' FOREIGN KEY'; $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; } $query .= ' ' . $columnList . $referencesClause; return $query; } /** * Returns the SQL to create an index on a table on this platform. * * @param Table|string $table The name of the table on which the index is to be created. * * @return string * * @throws InvalidArgumentException */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } $name = $index->getQuotedName($this); $columns = $index->getColumns(); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } if ($index->isPrimary()) { return $this->getCreatePrimaryKeySQL($index, $table); } $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); return $query; } /** * Adds condition for partial index. * * @return string */ protected function getPartialIndexSQL(Index $index) { if ($this->supportsPartialIndexes() && $index->hasOption('where')) { return ' WHERE ' . $index->getOption('where'); } return ''; } /** * Adds additional flags for index generation. * * @return string */ protected function getCreateIndexSQLFlags(Index $index) { return $index->isUnique() ? 'UNIQUE ' : ''; } /** * Returns the SQL to create an unnamed primary key constraint. * * @param Table|string $table * * @return string */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * Returns the SQL to create a named schema. * * @param string $schemaName * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateSchemaSQL($schemaName) { throw Exception::notSupported(__METHOD__); } /** * Quotes a string so that it can be safely used as a table or column name, * even if it is a reserved word of the platform. This also detects identifier * chains separated by dot and quotes them independently. * * NOTE: Just because you CAN use quoted identifiers doesn't mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteIdentifier($str) { if (strpos($str, '.') !== false) { $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str)); return implode('.', $parts); } return $this->quoteSingleIdentifier($str); } /** * Quotes a single identifier (no dot chain separation). * * @param string $str The identifier name to be quoted. * * @return string The quoted identifier string. */ public function quoteSingleIdentifier($str) { $c = $this->getIdentifierQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Returns the SQL to create a new foreign key. * * @param ForeignKeyConstraint $foreignKey The foreign key constraint. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return string */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); } /** * Gets the SQL statements for altering an existing table. * * This method returns an array of SQL statements, since some platforms need several statements. * * @return string[] * * @throws Exception If not supported on this platform. */ public function getAlterTableSQL(TableDiff $diff) { throw Exception::notSupported(__METHOD__); } /** * @param mixed[] $columnSql * * @return bool */ protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { return false; } $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { return false; } $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { return false; } $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string $oldColumnName * @param string[] $columnSql * * @return bool */ protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { return false; } $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); $columnSql = array_merge($columnSql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @param string[] $sql * * @return bool */ protected function onSchemaAlterTable(TableDiff $diff, &$sql) { if ($this->_eventManager === null) { return false; } if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { return false; } $eventArgs = new SchemaAlterTableEventArgs($diff, $this); $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); $sql = array_merge($sql, $eventArgs->getSql()); return $eventArgs->isDefaultPrevented(); } /** * @return string[] */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $tableName = $diff->getName($this)->getQuotedName($this); $sql = []; if ($this->supportsForeignKeyConstraints()) { foreach ($diff->removedForeignKeys as $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); } foreach ($diff->changedForeignKeys as $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); } } foreach ($diff->removedIndexes as $index) { $sql[] = $this->getDropIndexSQL($index, $tableName); } foreach ($diff->changedIndexes as $index) { $sql[] = $this->getDropIndexSQL($index, $tableName); } return $sql; } /** * @return string[] */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableName = $newName->getQuotedName($this); } else { $tableName = $diff->getName($this)->getQuotedName($this); } if ($this->supportsForeignKeyConstraints()) { foreach ($diff->addedForeignKeys as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); } foreach ($diff->changedForeignKeys as $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); } } foreach ($diff->addedIndexes as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableName); } foreach ($diff->changedIndexes as $index) { $sql[] = $this->getCreateIndexSQL($index, $tableName); } foreach ($diff->renamedIndexes as $oldIndexName => $index) { $oldIndexName = new Identifier($oldIndexName); $sql = array_merge( $sql, $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName) ); } return $sql; } /** * Returns the SQL for renaming an index on a table. * * @param string $oldIndexName The name of the index to rename from. * @param Index $index The definition of the index to rename to. * @param string $tableName The table to rename the given index on. * * @return string[] The sequence of SQL statements for renaming the given index. */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [ $this->getDropIndexSQL($oldIndexName, $tableName), $this->getCreateIndexSQL($index, $tableName), ]; } /** * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions. * * @deprecated * * @return string[] */ protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) { return array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff) ); } /** * Gets declaration of a number of columns in bulk. * * @param mixed[][] $columns A multidimensional associative array. * The first dimension determines the column name, while the second * dimension is keyed with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * * @return string */ public function getColumnDeclarationListSQL(array $columns) { $declarations = []; foreach ($columns as $name => $column) { $declarations[] = $this->getColumnDeclarationSQL($name, $column); } return implode(', ', $declarations); } /** * Obtains DBMS specific SQL code portion needed to declare a generic type * column to be used in statements like CREATE TABLE. * * @param string $name The name the column to be declared. * @param mixed[] $column An associative array with the name of the properties * of the column being declared as array indexes. Currently, the types * of supported column properties are as follows: * * length * Integer value that determines the maximum length of the text * column. If this argument is missing the column should be * declared to have the longest length allowed by the DBMS. * * default * Text value to be used as default for this column. * * notnull * Boolean flag that indicates whether this column is constrained * to not be set to null. * charset * Text value with the default CHARACTER SET for this column. * collation * Text value with the default COLLATION for this column. * unique * unique constraint * check * column check constraint * columnDefinition * a string that defines the complete column * * @return string DBMS specific SQL code portion that should be used to declare the column. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $declaration = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $charset = isset($column['charset']) && $column['charset'] ? ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; $collation = isset($column['collation']) && $column['collation'] ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = isset($column['notnull']) && $column['notnull'] ? ' NOT NULL' : ''; $unique = isset($column['unique']) && $column['unique'] ? ' ' . $this->getUniqueFieldDeclarationSQL() : ''; $check = isset($column['check']) && $column['check'] ? ' ' . $column['check'] : ''; $typeDecl = $column['type']->getSQLDeclaration($column, $this); $declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); } } return $name . ' ' . $declaration; } /** * Returns the SQL snippet that declares a floating point column of arbitrary precision. * * @param mixed[] $column * * @return string */ public function getDecimalTypeDeclarationSQL(array $column) { $column['precision'] = ! isset($column['precision']) || empty($column['precision']) ? 10 : $column['precision']; $column['scale'] = ! isset($column['scale']) || empty($column['scale']) ? 0 : $column['scale']; return 'NUMERIC(' . $column['precision'] . ', ' . $column['scale'] . ')'; } /** * Obtains DBMS specific SQL code portion needed to set a default value * declaration to be used in statements like CREATE TABLE. * * @param mixed[] $column The column definition array. * * @return string DBMS specific SQL code portion needed to set a default value. */ public function getDefaultValueDeclarationSQL($column) { if (! isset($column['default'])) { return empty($column['notnull']) ? ' DEFAULT NULL' : ''; } $default = $column['default']; if (! isset($column['type'])) { return " DEFAULT '" . $default . "'"; } $type = $column['type']; if ($type instanceof Types\PhpIntegerMappingType) { return ' DEFAULT ' . $default; } if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { return ' DEFAULT ' . $this->getCurrentTimestampSQL(); } if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) { return ' DEFAULT ' . $this->getCurrentTimeSQL(); } if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) { return ' DEFAULT ' . $this->getCurrentDateSQL(); } if ($type instanceof Types\BooleanType) { return " DEFAULT '" . $this->convertBooleans($default) . "'"; } return ' DEFAULT ' . $this->quoteStringLiteral($default); } /** * Obtains DBMS specific SQL code portion needed to set a CHECK constraint * declaration to be used in statements like CREATE TABLE. * * @param string[]|mixed[][] $definition The check definition. * * @return string DBMS specific SQL code portion needed to set a CHECK constraint. */ public function getCheckDeclarationSQL(array $definition) { $constraints = []; foreach ($definition as $column => $def) { if (is_string($def)) { $constraints[] = 'CHECK (' . $def . ')'; } else { if (isset($def['min'])) { $constraints[] = 'CHECK (' . $column . ' >= ' . $def['min'] . ')'; } if (isset($def['max'])) { $constraints[] = 'CHECK (' . $column . ' <= ' . $def['max'] . ')'; } } } return implode(', ', $constraints); } /** * Obtains DBMS specific SQL code portion needed to set a unique * constraint declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the unique constraint. * @param Index $index The index definition. * * @return string DBMS specific SQL code portion needed to set a constraint. * * @throws InvalidArgumentException */ public function getUniqueConstraintDeclarationSQL($name, Index $index) { $columns = $index->getColumns(); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param string $name The name of the index. * @param Index $index The index definition. * * @return string DBMS specific SQL code portion needed to set an index. * * @throws InvalidArgumentException */ public function getIndexDeclarationSQL($name, Index $index) { $columns = $index->getColumns(); $name = new Identifier($name); if (count($columns) === 0) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); } /** * Obtains SQL code portion needed to create a custom column, * e.g. when a column has the "columnDefinition" keyword. * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. * * @param mixed[] $column * * @return string */ public function getCustomTypeDeclarationSQL(array $column) { return $column['columnDefinition']; } /** * Obtains DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param mixed[]|Index $columnsOrIndex array declaration is deprecated, prefer passing Index to this method */ public function getIndexFieldDeclarationListSQL($columnsOrIndex): string { if ($columnsOrIndex instanceof Index) { return implode(', ', $columnsOrIndex->getQuotedColumns($this)); } if (! is_array($columnsOrIndex)) { throw new InvalidArgumentException('Fields argument should be an Index or array.'); } $ret = []; foreach ($columnsOrIndex as $column => $definition) { if (is_array($definition)) { $ret[] = $column; } else { $ret[] = $definition; } } return implode(', ', $ret); } /** * Returns the required SQL string that fits between CREATE ... TABLE * to create the table as a temporary table. * * Should be overridden in driver classes to return the correct string for the * specific database type. * * The default is to return the string "TEMPORARY" - this will result in a * SQL error for any database that does not support temporary tables, or that * requires a different SQL command from "CREATE TEMPORARY TABLE". * * @return string The string required to be placed between "CREATE" and "TABLE" * to generate a temporary table, if possible. */ public function getTemporaryTableSQL() { return 'TEMPORARY'; } /** * Some vendors require temporary table names to be qualified specially. * * @param string $tableName * * @return string */ public function getTemporaryTableName($tableName) { return $tableName; } /** * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); return $sql; } /** * Returns the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param ForeignKeyConstraint $foreignKey The foreign key definition. * * @return string */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) { $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); } if ($foreignKey->hasOption('onDelete')) { $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } return $query; } /** * Returns the given referential action in uppercase if valid, otherwise throws an exception. * * @param string $action The foreign key referential action. * * @return string * * @throws InvalidArgumentException If unknown referential action given. */ public function getForeignKeyReferentialActionSQL($action) { $upper = strtoupper($action); switch ($upper) { case 'CASCADE': case 'SET NULL': case 'NO ACTION': case 'RESTRICT': case 'SET DEFAULT': return $upper; default: throw new InvalidArgumentException('Invalid foreign key action: ' . $upper); } } /** * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string * * @throws InvalidArgumentException */ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = ''; if (strlen($foreignKey->getName())) { $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; } $sql .= 'FOREIGN KEY ('; if (count($foreignKey->getLocalColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'local' required."); } if (count($foreignKey->getForeignColumns()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); } if (strlen($foreignKey->getForeignTableName()) === 0) { throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); } return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) . ') REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . ' (' . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; } /** * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration to be used in statements like CREATE TABLE. * * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint * of a column declaration. */ public function getUniqueFieldDeclarationSQL() { return 'UNIQUE'; } /** * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration to be used in statements like CREATE TABLE. * * @param string $charset The name of the charset. * * @return string DBMS specific SQL code portion needed to set the CHARACTER SET * of a column declaration. */ public function getColumnCharsetDeclarationSQL($charset) { return ''; } /** * Obtains DBMS specific SQL code portion needed to set the COLLATION * of a column declaration to be used in statements like CREATE TABLE. * * @param string $collation The name of the collation. * * @return string DBMS specific SQL code portion needed to set the COLLATION * of a column declaration. */ public function getColumnCollationDeclarationSQL($collation) { return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : ''; } /** * Whether the platform prefers sequences for ID generation. * Subclasses should override this method to return TRUE if they prefer sequences. * * @deprecated * * @return bool */ public function prefersSequences() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::prefersSequences() is deprecated without replacement and removed in DBAL 3.0' ); return false; } /** * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. * Subclasses should override this method to return TRUE if they prefer identity columns. * * @return bool */ public function prefersIdentityColumns() { return false; } /** * Some platforms need the boolean values to be converted. * * The default conversion in this implementation converts to integers (false => 0, true => 1). * * Note: if the input is not a boolean the original input might be returned. * * There are two contexts when converting booleans: Literals and Prepared Statements. * This method should handle the literal case * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $k => $value) { if (! is_bool($value)) { continue; } $item[$k] = (int) $value; } } elseif (is_bool($item)) { $item = (int) $item; } return $item; } /** * Some platforms have boolean literals that needs to be correctly converted * * The default conversion tries to convert value into bool "(bool)$item" * * @param mixed $item * * @return bool|null */ public function convertFromBoolean($item) { return $item === null ? null : (bool) $item; } /** * This method should handle the prepared statements case. When there is no * distinction, it's OK to use the same method. * * Note: if the input is not a boolean the original input might be returned. * * @param mixed $item A boolean or an array of them. * * @return mixed A boolean database value or an array of them. */ public function convertBooleansToDatabaseValue($item) { return $this->convertBooleans($item); } /** * Returns the SQL specific for the platform to get the current date. * * @return string */ public function getCurrentDateSQL() { return 'CURRENT_DATE'; } /** * Returns the SQL specific for the platform to get the current time. * * @return string */ public function getCurrentTimeSQL() { return 'CURRENT_TIME'; } /** * Returns the SQL specific for the platform to get the current timestamp * * @return string */ public function getCurrentTimestampSQL() { return 'CURRENT_TIMESTAMP'; } /** * Returns the SQL for a given transaction isolation level Connection constant. * * @param int $level * * @return string * * @throws InvalidArgumentException */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: return 'REPEATABLE READ'; case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: throw new InvalidArgumentException('Invalid isolation level:' . $level); } } /** * @return string * * @throws Exception If not supported on this platform. */ public function getListDatabasesSQL() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL statement for retrieving the namespaces defined in the database. * * @return string * * @throws Exception If not supported on this platform. */ public function getListNamespacesSQL() { throw Exception::notSupported(__METHOD__); } /** * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListSequencesSQL($database) { throw Exception::notSupported(__METHOD__); } /** * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableConstraintsSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableColumnsSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @return string * * @throws Exception If not supported on this platform. */ public function getListTablesSQL() { throw Exception::notSupported(__METHOD__); } /** * @return string * * @throws Exception If not supported on this platform. */ public function getListUsersSQL() { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to list all views of a database or user. * * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListViewsSQL($database) { throw Exception::notSupported(__METHOD__); } /** * Returns the list of indexes for the current database. * * The current database parameter is optional but will always be passed * when using the SchemaManager API and is the database the given table is in. * * Attention: Some platforms only support currentDatabase when they * are connected with that database. Cross-database information schema * requests may be impossible. * * @param string $table * @param string $database * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableIndexesSQL($table, $database = null) { throw Exception::notSupported(__METHOD__); } /** * @param string $table * * @return string * * @throws Exception If not supported on this platform. */ public function getListTableForeignKeysSQL($table) { throw Exception::notSupported(__METHOD__); } /** * @param string $name * @param string $sql * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateViewSQL($name, $sql) { throw Exception::notSupported(__METHOD__); } /** * @param string $name * * @return string * * @throws Exception If not supported on this platform. */ public function getDropViewSQL($name) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL snippet to drop an existing sequence. * * @param Sequence|string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getDropSequenceSQL($sequence) { throw Exception::notSupported(__METHOD__); } /** * @param string $sequence * * @return string * * @throws Exception If not supported on this platform. */ public function getSequenceNextValSQL($sequence) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to create a new database. * * @param string $name The name of the database that should be created. * * @return string * * @throws Exception If not supported on this platform. */ public function getCreateDatabaseSQL($name) { throw Exception::notSupported(__METHOD__); } /** * Returns the SQL to set the transaction isolation level. * * @param int $level * * @return string * * @throws Exception If not supported on this platform. */ public function getSetTransactionIsolationSQL($level) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime columns in * statements like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. * * @param mixed[] $column * * @return string */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return $this->getDateTimeTypeDeclarationSQL($column); } /** * Obtains DBMS specific SQL to be used to create date columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getDateTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * Obtains DBMS specific SQL to be used to create time columns in statements * like CREATE TABLE. * * @param mixed[] $column * * @return string * * @throws Exception If not supported on this platform. */ public function getTimeTypeDeclarationSQL(array $column) { throw Exception::notSupported(__METHOD__); } /** * @param mixed[] $column * * @return string */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION'; } /** * Gets the default transaction isolation level of the platform. * * @see TransactionIsolationLevel * * @return int The default isolation level. */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::READ_COMMITTED; } /* supports*() methods */ /** * Whether the platform supports sequences. * * @return bool */ public function supportsSequences() { return false; } /** * Whether the platform supports identity columns. * * Identity columns are columns that receive an auto-generated value from the * database on insert of a row. * * @return bool */ public function supportsIdentityColumns() { return false; } /** * Whether the platform emulates identity columns through sequences. * * Some platforms that do not support identity columns natively * but support sequences can emulate identity columns by using * sequences. * * @return bool */ public function usesSequenceEmulatedIdentityColumns() { return false; } /** * Returns the name of the sequence for a particular identity column in a particular table. * * @see usesSequenceEmulatedIdentityColumns * * @param string $tableName The name of the table to return the sequence name for. * @param string $columnName The name of the identity column in the table to return the sequence name for. * * @return string * * @throws Exception If not supported on this platform. */ public function getIdentitySequenceName($tableName, $columnName) { throw Exception::notSupported(__METHOD__); } /** * Whether the platform supports indexes. * * @return bool */ public function supportsIndexes() { return true; } /** * Whether the platform supports partial indexes. * * @return bool */ public function supportsPartialIndexes() { return false; } /** * Whether the platform supports indexes with column length definitions. */ public function supportsColumnLengthIndexes(): bool { return false; } /** * Whether the platform supports altering tables. * * @return bool */ public function supportsAlterTable() { return true; } /** * Whether the platform supports transactions. * * @return bool */ public function supportsTransactions() { return true; } /** * Whether the platform supports savepoints. * * @return bool */ public function supportsSavepoints() { return true; } /** * Whether the platform supports releasing savepoints. * * @return bool */ public function supportsReleaseSavepoints() { return $this->supportsSavepoints(); } /** * Whether the platform supports primary key constraints. * * @return bool */ public function supportsPrimaryConstraints() { return true; } /** * Whether the platform supports foreign key constraints. * * @return bool */ public function supportsForeignKeyConstraints() { return true; } /** * Whether this platform supports onUpdate in foreign key constraints. * * @deprecated * * @return bool */ public function supportsForeignKeyOnUpdate() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::supportsForeignKeyOnUpdate() is deprecated without replacement and removed in DBAL 3.0' ); return $this->supportsForeignKeyConstraints(); } /** * Whether the platform supports database schemas. * * @return bool */ public function supportsSchemas() { return false; } /** * Whether this platform can emulate schemas. * * Platforms that either support or emulate schemas don't automatically * filter a schema for the namespaced elements in {@link AbstractManager::createSchema()}. * * @return bool */ public function canEmulateSchemas() { return false; } /** * Returns the default schema name. * * @return string * * @throws Exception If not supported on this platform. */ public function getDefaultSchemaName() { throw Exception::notSupported(__METHOD__); } /** * Whether this platform supports create database. * * Some databases don't allow to create and drop databases at all or only with certain tools. * * @return bool */ public function supportsCreateDropDatabase() { return true; } /** * Whether the platform supports getting the affected rows of a recent update/delete type query. * * @return bool */ public function supportsGettingAffectedRows() { return true; } /** * Whether this platform support to add inline column comments as postfix. * * @return bool */ public function supportsInlineColumnComments() { return false; } /** * Whether this platform support the proprietary syntax "COMMENT ON asset". * * @return bool */ public function supportsCommentOnStatement() { return false; } /** * Does this platform have native guid type. * * @return bool */ public function hasNativeGuidType() { return false; } /** * Does this platform have native JSON type. * * @return bool */ public function hasNativeJsonType() { return false; } /** * @deprecated * * @return string * * @todo Remove in 3.0 */ public function getIdentityColumnNullInsertSQL() { return ''; } /** * Whether this platform supports views. * * @return bool */ public function supportsViews() { return true; } /** * Does this platform support column collation? * * @return bool */ public function supportsColumnCollation() { return false; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime value of this platform. * * @return string The format string. */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime with timezone value of this platform. * * @return string The format string. */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored date value of this platform. * * @return string The format string. */ public function getDateFormatString() { return 'Y-m-d'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored time value of this platform. * * @return string The format string. */ public function getTimeFormatString() { return 'H:i:s'; } /** * Adds an driver-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int|null $offset * * @return string * * @throws Exception */ final public function modifyLimitQuery($query, $limit, $offset = null) { if ($limit !== null) { $limit = (int) $limit; } $offset = (int) $offset; if ($offset < 0) { throw new Exception(sprintf( 'Offset must be a positive integer or zero, %d given', $offset )); } if ($offset > 0 && ! $this->supportsLimitOffset()) { throw new Exception(sprintf( 'Platform %s does not support offset values in limit queries.', $this->getName() )); } return $this->doModifyLimitQuery($query, $limit, $offset); } /** * Adds an platform-specific LIMIT clause to the query. * * @param string $query * @param int|null $limit * @param int|null $offset * * @return string */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= ' LIMIT ' . $limit; } if ($offset > 0) { $query .= ' OFFSET ' . $offset; } return $query; } /** * Whether the database platform support offsets in modify limit clauses. * * @return bool */ public function supportsLimitOffset() { return true; } /** * Gets the character casing of a column in an SQL result set of this platform. * * @deprecated * * @param string $column The column name for which to get the correct character casing. * * @return string The column name in the character casing used in SQL result sets. */ public function getSQLResultCasing($column) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::getSQLResultCasing is deprecated without replacement and removed in DBAL 3.' . 'Use Portability\Connection with PORTABILITY_FIX_CASE to get portable result cases.' ); return $column; } /** * Makes any fixes to a name of a schema element (table, sequence, ...) that are required * by restrictions of the platform, like a maximum length. * * @deprecated * * @param string $schemaElementName * * @return string */ public function fixSchemaElementName($schemaElementName) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4132', 'AbstractPlatform::fixSchemaElementName is deprecated with no replacement and removed in DBAL 3.0' ); return $schemaElementName; } /** * Maximum length of any given database identifier, like tables or column names. * * @return int */ public function getMaxIdentifierLength() { return 63; } /** * Returns the insert SQL for an empty insert statement. * * @param string $quotedTableName * @param string $quotedIdentifierColumnName * * @return string */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; } /** * Generates a Truncate Table SQL statement for a given table. * * Cascade is not supported on many platforms but would optionally cascade the truncate by * following the foreign keys. * * @param string $tableName * @param bool $cascade * * @return string */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); } /** * This is for test reasons, many vendors have special requirements for dummy statements. * * @return string */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s', $expression); } /** * Returns the SQL to create a new savepoint. * * @param string $savepoint * * @return string */ public function createSavePoint($savepoint) { return 'SAVEPOINT ' . $savepoint; } /** * Returns the SQL to release a savepoint. * * @param string $savepoint * * @return string */ public function releaseSavePoint($savepoint) { return 'RELEASE SAVEPOINT ' . $savepoint; } /** * Returns the SQL to rollback a savepoint. * * @param string $savepoint * * @return string */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TO SAVEPOINT ' . $savepoint; } /** * Returns the keyword list instance of this platform. * * @return KeywordList * * @throws Exception If no keyword list is specified. */ final public function getReservedKeywordsList() { // Check for an existing instantiation of the keywords class. if ($this->_keywords) { return $this->_keywords; } $class = $this->getReservedKeywordsClass(); $keywords = new $class(); if (! $keywords instanceof KeywordList) { throw Exception::notSupported(__METHOD__); } // Store the instance so it doesn't need to be generated on every request. $this->_keywords = $keywords; return $keywords; } /** * Returns the class name of the reserved keywords list. * * @return string * @psalm-return class-string<KeywordList> * * @throws Exception If not supported on this platform. */ protected function getReservedKeywordsClass() { throw Exception::notSupported(__METHOD__); } /** * Quotes a literal string. * This method is NOT meant to fix SQL injections! * It is only meant to escape this platform's string literal * quote character inside the given literal string. * * @param string $str The literal string to be quoted. * * @return string The quoted literal string. */ public function quoteStringLiteral($str) { $c = $this->getStringLiteralQuoteCharacter(); return $c . str_replace($c, $c . $c, $str) . $c; } /** * Gets the character used for string literal quoting. * * @return string */ public function getStringLiteralQuoteCharacter() { return "'"; } /** * Escapes metacharacters in a string intended to be used with a LIKE * operator. * * @param string $inputString a literal, unquoted string * @param string $escapeChar should be reused by the caller in the LIKE * expression. */ final public function escapeStringForLike(string $inputString, string $escapeChar): string { return preg_replace( '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', addcslashes($escapeChar, '\\') . '$1', $inputString ); } protected function getLikeWildcardCharacters(): string { return '%_'; } } dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php 0000644 00000061237 15120025741 0015227 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use function array_merge; use function count; use function current; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function sprintf; use function strpos; use function strtoupper; class DB2Platform extends AbstractPlatform { public function getCharMaxLength(): int { return 254; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 32704; } /** * {@inheritdoc} */ public function getBinaryDefaultLength() { return 1; } /** * {@inheritDoc} */ public function getVarcharTypeDeclarationSQL(array $column) { // for IBM DB2, the CHAR max length is less than VARCHAR default length if (! isset($column['length']) && ! empty($column['fixed'])) { $column['length'] = $this->getCharMaxLength(); } return parent::getVarcharTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { // todo blob(n) with $column['length']; return 'BLOB(1M)'; } /** * {@inheritDoc} */ public function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'smallint' => 'smallint', 'bigint' => 'bigint', 'integer' => 'integer', 'time' => 'time', 'date' => 'date', 'varchar' => 'string', 'character' => 'string', 'varbinary' => 'binary', 'binary' => 'binary', 'clob' => 'text', 'blob' => 'blob', 'decimal' => 'decimal', 'double' => 'float', 'real' => 'float', 'timestamp' => 'datetime', ]; } /** * {@inheritdoc} */ public function isCommentedDoctrineType(Type $doctrineType) { if ($doctrineType->getName() === Types::BOOLEAN) { // We require a commented boolean type in order to distinguish between boolean and smallint // as both (have to) map to the same native type. return true; } return parent::isCommentedDoctrineType($doctrineType); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(254)') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed) . ' FOR BIT DATA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { // todo clob(n) with $column['length']; return 'CLOB(1M)'; } /** * {@inheritDoc} */ public function getName() { return 'db2'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'SMALLINT'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; } return $autoinc; } /** * {@inheritdoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritdoc} */ public function getBitOrComparisonExpression($value1, $value2) { return 'BITOR(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::WEEK: $interval *= 7; $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval *= 3; $unit = DateIntervalUnit::MONTH; break; } return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit; } /** * {@inheritdoc} */ public function getDateDiffExpression($date1, $date2) { return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP(0) WITH DEFAULT'; } return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritdoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; } /** * This code fragment is originally from the Zend_Db_Adapter_Db2 class, but has been edited. * * @param string $table * @param string $database * * @return string */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); // We do the funky subquery and join syscat.columns.default this crazy way because // as of db2 v10, the column is CLOB(64k) and the distinct operator won't allow a CLOB, // it wants shorter stuff like a varchar. return " SELECT cols.default, subq.* FROM ( SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, c.typename, c.nulls, c.length, c.scale, c.identity, tc.type AS tabconsttype, c.remarks AS comment, k.colseq, CASE WHEN c.generated = 'D' THEN 1 ELSE 0 END AS autoincrement FROM syscat.columns c LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc ON (k.tabschema = tc.tabschema AND k.tabname = tc.tabname AND tc.type = 'P')) ON (c.tabschema = k.tabschema AND c.tabname = k.tabname AND c.colname = k.colname) WHERE UPPER(c.tabname) = UPPER(" . $table . ') ORDER BY c.colno ) subq JOIN syscat.columns cols ON subq.tabschema = cols.tabschema AND subq.tabname = cols.tabname AND subq.colno = cols.colno ORDER BY subq.colno '; } /** * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'"; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS'; } /** * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); return "SELECT idx.INDNAME AS key_name, idxcol.COLNAME AS column_name, CASE WHEN idx.UNIQUERULE = 'P' THEN 1 ELSE 0 END AS primary, CASE WHEN idx.UNIQUERULE = 'D' THEN 1 ELSE 0 END AS non_unique FROM SYSCAT.INDEXES AS idx JOIN SYSCAT.INDEXCOLUSE AS idxcol ON idx.INDSCHEMA = idxcol.INDSCHEMA AND idx.INDNAME = idxcol.INDNAME WHERE idx.TABNAME = UPPER(" . $table . ') ORDER BY idxcol.COLSEQ ASC'; } /** * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->quoteStringLiteral($table); return "SELECT fkcol.COLNAME AS local_column, fk.REFTABNAME AS foreign_table, pkcol.COLNAME AS foreign_column, fk.CONSTNAME AS index_name, CASE WHEN fk.UPDATERULE = 'R' THEN 'RESTRICT' ELSE NULL END AS on_update, CASE WHEN fk.DELETERULE = 'C' THEN 'CASCADE' WHEN fk.DELETERULE = 'N' THEN 'SET NULL' WHEN fk.DELETERULE = 'R' THEN 'RESTRICT' ELSE NULL END AS on_delete FROM SYSCAT.REFERENCES AS fk JOIN SYSCAT.KEYCOLUSE AS fkcol ON fk.CONSTNAME = fkcol.CONSTNAME AND fk.TABSCHEMA = fkcol.TABSCHEMA AND fk.TABNAME = fkcol.TABNAME JOIN SYSCAT.KEYCOLUSE AS pkcol ON fk.REFKEYNAME = pkcol.CONSTNAME AND fk.REFTABSCHEMA = pkcol.TABSCHEMA AND fk.REFTABNAME = pkcol.TABNAME WHERE fk.TABNAME = UPPER(" . $table . ') ORDER BY fkcol.COLSEQ ASC'; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * {@inheritDoc} */ public function supportsCreateDropDatabase() { return false; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritdoc} */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getCurrentDateSQL() { return 'CURRENT DATE'; } /** * {@inheritDoc} */ public function getCurrentTimeSQL() { return 'CURRENT TIME'; } /** * {@inheritDoc} */ public function getCurrentTimestampSQL() { return 'CURRENT TIMESTAMP'; } /** * {@inheritDoc} */ public function getIndexDeclarationSQL($name, Index $index) { // Index declaration in statements like CREATE TABLE is not supported. throw Exception::notSupported(__METHOD__); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = []; if (isset($options['indexes'])) { $indexes = $options['indexes']; } $options['indexes'] = []; $sqls = parent::_getCreateTableSQL($name, $columns, $options); foreach ($indexes as $definition) { $sqls[] = $this->getCreateIndexSQL($definition, $name); } return $sqls; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $columnSql = []; $commentsSQL = []; $queryParts = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnDef = $column->toArray(); $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); // Adding non-nullable columns to a table requires a default value to be specified. if ( ! empty($columnDef['notnull']) && ! isset($columnDef['default']) && empty($columnDef['autoincrement']) ) { $queryPart .= ' WITH DEFAULT'; } $queryParts[] = $queryPart; $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $comment ); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($columnDiff->hasChanged('comment')) { $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $columnDiff->column->getQuotedName($this), $this->getColumnComment($columnDiff->column) ); if (count($columnDiff->changedProperties) === 1) { continue; } } $this->gatherAlterColumnSQL($diff->getName($this), $columnDiff, $sql, $queryParts); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(' ', $queryParts); } // Some table alteration operations require a table reorganization. if (! empty($diff->removedColumns) || ! empty($diff->changedColumns)) { $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $diff->getName($this)->getQuotedName($this) . "')"; } $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = sprintf( 'RENAME TABLE %s TO %s', $diff->getName($this)->getQuotedName($this), $newName->getQuotedName($this) ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * Gathers the table alteration SQL for a given column diff. * * @param Identifier $table The table to gather the SQL for. * @param ColumnDiff $columnDiff The column diff to evaluate. * @param string[] $sql The sequence of table alteration statements to fill. * @param mixed[] $queryParts The sequence of column alteration clauses to fill. */ private function gatherAlterColumnSQL( Identifier $table, ColumnDiff $columnDiff, array &$sql, array &$queryParts ): void { $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); if (empty($alterColumnClauses)) { return; } // If we have a single column alteration, we can append the clause to the main query. if (count($alterColumnClauses) === 1) { $queryParts[] = current($alterColumnClauses); return; } // We have multiple alterations for the same column, // so we need to trigger a complete ALTER TABLE statement // for each ALTER COLUMN clause. foreach ($alterColumnClauses as $alterColumnClause) { $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ' . $alterColumnClause; } } /** * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. * * @param ColumnDiff $columnDiff The column diff to evaluate. * * @return string[] */ private function getAlterColumnClausesSQL(ColumnDiff $columnDiff) { $column = $columnDiff->column->toArray(); $alterClause = 'ALTER COLUMN ' . $columnDiff->column->getQuotedName($this); if ($column['columnDefinition']) { return [$alterClause . ' ' . $column['columnDefinition']]; } $clauses = []; if ( $columnDiff->hasChanged('type') || $columnDiff->hasChanged('length') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed') ) { $clauses[] = $alterClause . ' SET DATA TYPE ' . $column['type']->getSQLDeclaration($column, $this); } if ($columnDiff->hasChanged('notnull')) { $clauses[] = $column['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; } if ($columnDiff->hasChanged('default')) { if (isset($column['default'])) { $defaultClause = $this->getDefaultValueDeclarationSQL($column); if ($defaultClause) { $clauses[] = $alterClause . ' SET' . $defaultClause; } } else { $clauses[] = $alterClause . ' DROP DEFAULT'; } } return $clauses; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $table = $diff->getName($this)->getQuotedName($this); foreach ($diff->removedIndexes as $remKey => $remIndex) { foreach ($diff->addedIndexes as $addKey => $addIndex) { if ($remIndex->getColumns() !== $addIndex->getColumns()) { continue; } if ($remIndex->isPrimary()) { $sql[] = 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; } elseif ($remIndex->isUnique()) { $sql[] = 'ALTER TABLE ' . $table . ' DROP UNIQUE ' . $remIndex->getQuotedName($this); } else { $sql[] = $this->getDropIndexSQL($remIndex, $table); } $sql[] = $this->getCreateIndexSQL($addIndex, $table); unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]); break; } } $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); return $sql; } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} */ public function getDefaultValueDeclarationSQL($column) { if (! empty($column['autoincrement'])) { return ''; } if (isset($column['version']) && $column['version']) { if ((string) $column['type'] !== 'DateTime') { $column['default'] = '1'; } } return parent::getDefaultValueDeclarationSQL($column); } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'DECLARE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return 'SESSION.' . $tableName; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset = null) { $where = []; if ($offset > 0) { $where[] = sprintf('db22.DC_ROWNUM >= %d', $offset + 1); } if ($limit !== null) { $where[] = sprintf('db22.DC_ROWNUM <= %d', $offset + $limit); } if (empty($where)) { return $query; } // Todo OVER() needs ORDER BY data! return sprintf( 'SELECT db22.* FROM (SELECT db21.*, ROW_NUMBER() OVER() AS DC_ROWNUM FROM (%s) db21) db22 WHERE %s', $query, implode(' AND ', $where) ); } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTR(' . $string . ', ' . $start . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function prefersIdentityColumns() { return true; } /** * {@inheritDoc} * * DB2 returns all column names in SQL result sets in uppercase. * * @deprecated */ public function getSQLResultCasing($column) { return strtoupper($column); } /** * {@inheritDoc} */ public function getForUpdateSQL() { return ' WITH RR USE AND KEEP UPDATE LOCKS'; } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression); } /** * {@inheritDoc} * * DB2 supports savepoints, but they work semantically different than on other vendor platforms. * * TODO: We have to investigate how to get DB2 up and running with savepoints. */ public function supportsSavepoints() { return false; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\DB2Keywords::class; } public function getListTableCommentsSQL(string $table): string { return sprintf( <<<'SQL' SELECT REMARKS FROM SYSIBM.SYSTABLES WHERE NAME = UPPER( %s ) SQL , $this->quoteStringLiteral($table) ); } } dbal/lib/Doctrine/DBAL/Platforms/DateIntervalUnit.php 0000644 00000000726 15120025741 0016371 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class DateIntervalUnit { public const SECOND = 'SECOND'; public const MINUTE = 'MINUTE'; public const HOUR = 'HOUR'; public const DAY = 'DAY'; public const WEEK = 'WEEK'; public const MONTH = 'MONTH'; public const QUARTER = 'QUARTER'; public const YEAR = 'YEAR'; /** * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php 0000644 00000041277 15120025741 0016305 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Types\BinaryType; use InvalidArgumentException; use function array_merge; use function array_unique; use function array_values; use function count; use function func_get_args; use function implode; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function sprintf; use function trim; /** * Drizzle platform * * @deprecated */ class DrizzlePlatform extends AbstractPlatform { /** * {@inheritDoc} */ public function getName() { return 'drizzle'; } /** * {@inheritDoc} */ public function getIdentifierQuoteCharacter() { return '`'; } /** * {@inheritDoc} */ public function getConcatExpression() { return 'CONCAT(' . implode(', ', func_get_args()) . ')'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' AUTO_INCREMENT'; } return $autoinc; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'; } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'VARBINARY(' . ($length ?: 255) . ')'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'boolean' => 'boolean', 'varchar' => 'string', 'varbinary' => 'binary', 'integer' => 'integer', 'blob' => 'blob', 'decimal' => 'decimal', 'datetime' => 'datetime', 'date' => 'date', 'time' => 'time', 'text' => 'text', 'timestamp' => 'datetime', 'double' => 'float', 'bigint' => 'bigint', ]; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'TEXT'; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } // add all indexes if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } // attach all primary keys if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE '; if (! empty($options['temporary'])) { $query .= 'TEMPORARY '; } $query .= 'TABLE ' . $name . ' (' . $queryFields . ') '; $query .= $this->buildTableOptions($options); $query .= $this->buildPartitionOptions($options); $sql = [$query]; if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * Build SQL for table options * * @param mixed[] $options * * @return string */ private function buildTableOptions(array $options) { if (isset($options['table_options'])) { return $options['table_options']; } $tableOptions = []; // Collate if (! isset($options['collate'])) { $options['collate'] = 'utf8_unicode_ci'; } $tableOptions[] = sprintf('COLLATE %s', $options['collate']); // Engine if (! isset($options['engine'])) { $options['engine'] = 'InnoDB'; } $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); // Auto increment if (isset($options['auto_increment'])) { $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); } // Comment if (isset($options['comment'])) { $comment = trim($options['comment'], " '"); $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($comment)); } // Row format if (isset($options['row_format'])) { $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); } return implode(' ', $tableOptions); } /** * Build SQL for partition options. * * @param mixed[] $options * * @return string */ private function buildPartitionOptions(array $options) { return isset($options['partition_options']) ? ' ' . $options['partition_options'] : ''; } /** * {@inheritDoc} */ public function getListDatabasesSQL() { return "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE CATALOG_NAME='LOCAL'"; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\DrizzleKeywords::class; } /** * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE' AND TABLE_SCHEMA=DATABASE()"; } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { if ($database) { $databaseSQL = $this->quoteStringLiteral($database); } else { $databaseSQL = 'DATABASE()'; } return 'SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE, IS_AUTO_INCREMENT,' . ' CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT, NUMERIC_PRECISION, NUMERIC_SCALE, COLLATION_NAME' . ' FROM DATA_DICTIONARY.COLUMNS' . ' WHERE TABLE_SCHEMA=' . $databaseSQL . ' AND TABLE_NAME = ' . $this->quoteStringLiteral($table); } /** * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { if ($database) { $databaseSQL = $this->quoteStringLiteral($database); } else { $databaseSQL = 'DATABASE()'; } return 'SELECT CONSTRAINT_NAME, CONSTRAINT_COLUMNS, REFERENCED_TABLE_NAME, REFERENCED_TABLE_COLUMNS,' . ' UPDATE_RULE, DELETE_RULE' . ' FROM DATA_DICTIONARY.FOREIGN_KEYS' . ' WHERE CONSTRAINT_SCHEMA=' . $databaseSQL . ' AND CONSTRAINT_TABLE=' . $this->quoteStringLiteral($table); } /** * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { if ($database) { $databaseSQL = $this->quoteStringLiteral($database); } else { $databaseSQL = 'DATABASE()'; } return "SELECT INDEX_NAME AS 'key_name'," . " COLUMN_NAME AS 'column_name'," . " IS_USED_IN_PRIMARY AS 'primary'," . " IS_UNIQUE=0 AS 'non_unique'" . ' FROM DATA_DICTIONARY.INDEX_PARTS' . ' WHERE TABLE_SCHEMA=' . $databaseSQL . ' AND TABLE_NAME=' . $this->quoteStringLiteral($table); } /** * {@inheritDoc} */ public function prefersIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} */ public function supportsViews() { return false; } /** * {@inheritdoc} */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { $indexName = $index->getQuotedName($this); } elseif (is_string($index)) { $indexName = $index; } else { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' ); } if ($table instanceof Table) { $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' ); } if ($index instanceof Index && $index->isPrimary()) { // drizzle primary keys are always named "PRIMARY", // so we cannot use them in statements because of them being keyword. return $this->getDropPrimaryKeySQL($table); } return 'DROP INDEX ' . $indexName . ' ON ' . $table; } /** * @param string $table * * @return string */ protected function getDropPrimaryKeySQL($table) { return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP'; } return 'DATETIME'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $columnSql = []; $queryParts = []; $newName = $diff->getNewName(); if ($newName !== false) { $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this); } foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnArray = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP ' . $column->getQuotedName($this); } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $column = $columnDiff->column; $columnArray = $column->toArray(); // Do not generate column alteration clause if type is binary and only fixed property has changed. // Drizzle only supports binary type columns with variable length. // Avoids unnecessary table alteration statements. if ( $columnArray['type'] instanceof BinaryType && $columnDiff->hasChanged('fixed') && count($columnDiff->changedProperties) === 1 ) { continue; } $columnArray['comment'] = $this->getColumnComment($column); $queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $columnArray = $column->toArray(); $columnArray['comment'] = $this->getColumnComment($column); $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(', ', $queryParts); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' ); } return 'DROP TEMPORARY TABLE ' . $table; } /** * {@inheritDoc} */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (! is_bool($value) && ! is_numeric($value)) { continue; } $item[$key] = $value ? 'true' : 'false'; } } elseif (is_bool($item) || is_numeric($item)) { $item = $item ? 'true' : 'false'; } return $item; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'UUID()'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'RLIKE'; } } dbal/lib/Doctrine/DBAL/Platforms/MariaDb1027Platform.php 0000644 00000001464 15120025741 0016465 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\Types; /** * Provides the behavior, features and SQL dialect of the MariaDB 10.2 (10.2.7 GA) database platform. * * Note: Should not be used with versions prior to 10.2.7. */ class MariaDb1027Platform extends MySqlPlatform { /** * {@inheritdoc} * * @link https://mariadb.com/kb/en/library/json-data-type/ */ public function getJsonTypeDeclarationSQL(array $column): string { return 'LONGTEXT'; } protected function getReservedKeywordsClass(): string { return Keywords\MariaDb102Keywords::class; } protected function initializeDoctrineTypeMappings(): void { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } } dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php 0000644 00000002662 15120025741 0015776 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Types\Types; /** * Provides the behavior, features and SQL dialect of the MySQL 5.7 (5.7.9 GA) database platform. */ class MySQL57Platform extends MySqlPlatform { /** * {@inheritdoc} */ public function hasNativeJsonType() { return true; } /** * {@inheritdoc} */ public function getJsonTypeDeclarationSQL(array $column) { return 'JSON'; } /** * {@inheritdoc} */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritdoc} */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { return []; } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\MySQL57Keywords::class; } /** * {@inheritdoc} */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } } dbal/lib/Doctrine/DBAL/Platforms/MySQL80Platform.php 0000644 00000000530 15120025741 0015762 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Provides the behavior, features and SQL dialect of the MySQL 8.0 (8.0 GA) database platform. */ class MySQL80Platform extends MySQL57Platform { /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\MySQL80Keywords::class; } } dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php 0000644 00000104474 15120025741 0015726 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\TextType; use InvalidArgumentException; use function array_diff_key; use function array_merge; use function array_unique; use function array_values; use function count; use function func_get_args; use function implode; use function in_array; use function is_numeric; use function is_string; use function sprintf; use function str_replace; use function strtoupper; use function trim; /** * The MySqlPlatform provides the behavior, features and SQL dialect of the * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that * uses the InnoDB storage engine. * * @todo Rename: MySQLPlatform */ class MySqlPlatform extends AbstractPlatform { public const LENGTH_LIMIT_TINYTEXT = 255; public const LENGTH_LIMIT_TEXT = 65535; public const LENGTH_LIMIT_MEDIUMTEXT = 16777215; public const LENGTH_LIMIT_TINYBLOB = 255; public const LENGTH_LIMIT_BLOB = 65535; public const LENGTH_LIMIT_MEDIUMBLOB = 16777215; /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit !== null) { $query .= ' LIMIT ' . $limit; if ($offset > 0) { $query .= ' OFFSET ' . $offset; } } elseif ($offset > 0) { // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible $query .= ' LIMIT 18446744073709551615 OFFSET ' . $offset; } return $query; } /** * {@inheritDoc} */ public function getIdentifierQuoteCharacter() { return '`'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'RLIKE'; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'UUID()'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } return 'LOCATE(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getConcatExpression() { return sprintf('CONCAT(%s)', implode(', ', func_get_args())); } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; } /** * {@inheritDoc} */ public function getListDatabasesSQL() { return 'SHOW DATABASES'; } /** * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { return 'SHOW INDEX FROM ' . $table; } /** * {@inheritDoc} * * Two approaches to listing the table indexes. The information_schema is * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". */ public function getListTableIndexesSQL($table, $database = null) { if ($database) { $database = $this->quoteStringLiteral($database); $table = $this->quoteStringLiteral($table); return 'SELECT NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, COLUMN_NAME AS Column_Name,' . ' SUB_PART AS Sub_Part, INDEX_TYPE AS Index_Type' . ' FROM information_schema.STATISTICS WHERE TABLE_NAME = ' . $table . ' AND TABLE_SCHEMA = ' . $database . ' ORDER BY SEQ_IN_INDEX ASC'; } return 'SHOW INDEX FROM ' . $table; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { $database = $this->quoteStringLiteral($database); return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $database; } /** * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); if ($database !== null) { $database = $this->quoteStringLiteral($database); } $sql = 'SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ' . 'k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ' . 'FROM information_schema.key_column_usage k /*!50116 ' . 'INNER JOIN information_schema.referential_constraints c ON ' . ' c.constraint_name = k.constraint_name AND ' . ' c.table_name = ' . $table . ' */ WHERE k.table_name = ' . $table; $databaseNameSql = $database ?? 'DATABASE()'; return $sql . ' AND k.table_schema = ' . $databaseNameSql . ' /*!50116 AND c.constraint_schema = ' . $databaseNameSql . ' */' . ' AND k.`REFERENCED_COLUMN_NAME` is not NULL'; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')'; } /** * Gets the SQL snippet used to declare a CLOB column type. * TINYTEXT : 2 ^ 8 - 1 = 255 * TEXT : 2 ^ 16 - 1 = 65535 * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 * LONGTEXT : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYTEXT) { return 'TINYTEXT'; } if ($length <= static::LENGTH_LIMIT_TEXT) { return 'TEXT'; } if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { return 'MEDIUMTEXT'; } } return 'LONGTEXT'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { if (isset($column['version']) && $column['version'] === true) { return 'TIMESTAMP'; } return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'TINYINT(1)'; } /** * Obtain DBMS specific SQL code portion needed to set the COLLATION * of a column declaration to be used in statements like CREATE TABLE. * * @deprecated Deprecated since version 2.5, Use {@link self::getColumnCollationDeclarationSQL()} instead. * * @param string $collation name of the collation * * @return string DBMS specific SQL code portion needed to set the COLLATION * of a column declaration. */ public function getCollationFieldDeclaration($collation) { return $this->getColumnCollationDeclarationSQL($collation); } /** * {@inheritDoc} * * MySql prefers "autoincrement" identity columns since sequences can only * be emulated with a table. */ public function prefersIdentityColumns() { return true; } /** * {@inheritDoc} * * MySql supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function getListTablesSQL() { return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->quoteStringLiteral($table); if ($database) { $database = $this->quoteStringLiteral($database); } else { $database = 'DATABASE()'; } return 'SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ' . 'COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, ' . 'CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS Collation ' . 'FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ' . $database . ' AND TABLE_NAME = ' . $table . ' ORDER BY ORDINAL_POSITION ASC'; } public function getListTableMetadataSQL(string $table, ?string $database = null): string { return sprintf( <<<'SQL' SELECT ENGINE, AUTO_INCREMENT, TABLE_COLLATION, TABLE_COMMENT, CREATE_OPTIONS FROM information_schema.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = %s AND TABLE_NAME = %s SQL , $database ? $this->quoteStringLiteral($database) : 'DATABASE()', $this->quoteStringLiteral($table) ); } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } // add all indexes if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } // attach all primary keys if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE '; if (! empty($options['temporary'])) { $query .= 'TEMPORARY '; } $query .= 'TABLE ' . $name . ' (' . $queryFields . ') '; $query .= $this->buildTableOptions($options); $query .= $this->buildPartitionOptions($options); $sql = [$query]; $engine = 'INNODB'; if (isset($options['engine'])) { $engine = strtoupper(trim($options['engine'])); } // Propagate foreign key constraints only for InnoDB. if (isset($options['foreignKeys']) && $engine === 'INNODB') { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * {@inheritdoc} */ public function getDefaultValueDeclarationSQL($column) { // Unset the default value if the given column definition does not allow default values. if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) { $column['default'] = null; } return parent::getDefaultValueDeclarationSQL($column); } /** * Build SQL for table options * * @param mixed[] $options * * @return string */ private function buildTableOptions(array $options) { if (isset($options['table_options'])) { return $options['table_options']; } $tableOptions = []; // Charset if (! isset($options['charset'])) { $options['charset'] = 'utf8'; } $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); // Collate if (! isset($options['collate'])) { $options['collate'] = $options['charset'] . '_unicode_ci'; } $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collate']); // Engine if (! isset($options['engine'])) { $options['engine'] = 'InnoDB'; } $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); // Auto increment if (isset($options['auto_increment'])) { $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); } // Comment if (isset($options['comment'])) { $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment'])); } // Row format if (isset($options['row_format'])) { $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); } return implode(' ', $tableOptions); } /** * Build SQL for partition options. * * @param mixed[] $options * * @return string */ private function buildPartitionOptions(array $options) { return isset($options['partition_options']) ? ' ' . $options['partition_options'] : ''; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $columnSql = []; $queryParts = []; $newName = $diff->getNewName(); if ($newName !== false) { $queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this); } foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnArray = array_merge($column->toArray(), [ 'comment' => $this->getColumnComment($column), ]); $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP ' . $column->getQuotedName($this); } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $column = $columnDiff->column; $columnArray = $column->toArray(); // Don't propagate default value changes for unsupported column types. if ( $columnDiff->hasChanged('default') && count($columnDiff->changedProperties) === 1 && ($columnArray['type'] instanceof TextType || $columnArray['type'] instanceof BlobType) ) { continue; } $columnArray['comment'] = $this->getColumnComment($column); $queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $columnArray = $column->toArray(); $columnArray['comment'] = $this->getColumnComment($column); $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); } if (isset($diff->addedIndexes['primary'])) { $keyColumns = array_unique(array_values($diff->addedIndexes['primary']->getColumns())); $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($diff->addedIndexes['primary']); } elseif (isset($diff->changedIndexes['primary'])) { // Necessary in case the new primary key includes a new auto_increment column foreach ($diff->changedIndexes['primary']->getColumns() as $columnName) { if (isset($diff->addedColumns[$columnName]) && $diff->addedColumns[$columnName]->getAutoincrement()) { $keyColumns = array_unique(array_values($diff->changedIndexes['primary']->getColumns())); $queryParts[] = 'DROP PRIMARY KEY'; $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; unset($diff->changedIndexes['primary']); break; } } } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . implode(', ', $queryParts); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { $sql = []; $table = $diff->getName($this)->getQuotedName($this); foreach ($diff->changedIndexes as $changedIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); } foreach ($diff->removedIndexes as $remKey => $remIndex) { $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $remIndex)); foreach ($diff->addedIndexes as $addKey => $addIndex) { if ($remIndex->getColumns() !== $addIndex->getColumns()) { continue; } $indexClause = 'INDEX ' . $addIndex->getName(); if ($addIndex->isPrimary()) { $indexClause = 'PRIMARY KEY'; } elseif ($addIndex->isUnique()) { $indexClause = 'UNIQUE INDEX ' . $addIndex->getName(); } $query = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $remIndex->getName() . ', '; $query .= 'ADD ' . $indexClause; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex) . ')'; $sql[] = $query; unset($diff->removedIndexes[$remKey], $diff->addedIndexes[$addKey]); break; } } $engine = 'INNODB'; if ($diff->fromTable instanceof Table && $diff->fromTable->hasOption('engine')) { $engine = strtoupper(trim($diff->fromTable->getOption('engine'))); } // Suppress foreign key constraint propagation on non-supporting engines. if ($engine !== 'INNODB') { $diff->addedForeignKeys = []; $diff->changedForeignKeys = []; $diff->removedForeignKeys = []; } $sql = array_merge( $sql, $this->getPreAlterTableAlterIndexForeignKeySQL($diff), parent::getPreAlterTableIndexForeignKeySQL($diff), $this->getPreAlterTableRenameIndexForeignKeySQL($diff) ); return $sql; } /** * @return string[] */ private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index) { $sql = []; if (! $index->isPrimary() || ! $diff->fromTable instanceof Table) { return $sql; } $tableName = $diff->getName($this)->getQuotedName($this); // Dropping primary keys requires to unset autoincrement attribute on the particular column first. foreach ($index->getColumns() as $columnName) { if (! $diff->fromTable->hasColumn($columnName)) { continue; } $column = $diff->fromTable->getColumn($columnName); if ($column->getAutoincrement() !== true) { continue; } $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $tableName . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // original autoincrement information might be needed later on by other parts of the table alteration $column->setAutoincrement(true); } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff) { $sql = []; $table = $diff->getName($this)->getQuotedName($this); foreach ($diff->changedIndexes as $changedIndex) { // Changed primary key if (! $changedIndex->isPrimary() || ! ($diff->fromTable instanceof Table)) { continue; } foreach ($diff->fromTable->getPrimaryKeyColumns() as $columnName) { $column = $diff->fromTable->getColumn($columnName); // Check if an autoincrement column was dropped from the primary key. if (! $column->getAutoincrement() || in_array($columnName, $changedIndex->getColumns())) { continue; } // The autoincrement attribute needs to be removed from the dropped column // before we can drop and recreate the primary key. $column->setAutoincrement(false); $sql[] = 'ALTER TABLE ' . $table . ' MODIFY ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); // Restore the autoincrement attribute as it might be needed later on // by other parts of the table alteration. $column->setAutoincrement(true); } } return $sql; } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $tableName = $diff->getName($this)->getQuotedName($this); foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->changedForeignKeys, true)) { continue; } $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); } return $sql; } /** * Returns the remaining foreign key constraints that require one of the renamed indexes. * * "Remaining" here refers to the diff between the foreign keys currently defined in the associated * table and the foreign keys to be removed. * * @param TableDiff $diff The table diff to evaluate. * * @return ForeignKeyConstraint[] */ private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff) { if (empty($diff->renamedIndexes) || ! $diff->fromTable instanceof Table) { return []; } $foreignKeys = []; /** @var ForeignKeyConstraint[] $remainingForeignKeys */ $remainingForeignKeys = array_diff_key( $diff->fromTable->getForeignKeys(), $diff->removedForeignKeys ); foreach ($remainingForeignKeys as $foreignKey) { foreach ($diff->renamedIndexes as $index) { if ($foreignKey->intersectsIndexColumns($index)) { $foreignKeys[] = $foreignKey; break; } } } return $foreignKeys; } /** * {@inheritdoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { return array_merge( parent::getPostAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableRenameIndexForeignKeySQL($diff) ); } /** * @param TableDiff $diff The table diff to gather the SQL for. * * @return string[] */ protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff) { $sql = []; $newName = $diff->getNewName(); if ($newName !== false) { $tableName = $newName->getQuotedName($this); } else { $tableName = $diff->getName($this)->getQuotedName($this); } foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { if (in_array($foreignKey, $diff->changedForeignKeys, true)) { continue; } $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); } return $sql; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } elseif ($index->hasFlag('fulltext')) { $type .= 'FULLTEXT '; } elseif ($index->hasFlag('spatial')) { $type .= 'SPATIAL '; } return $type; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getFloatDeclarationSQL(array $column) { return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column); } /** * {@inheritdoc} */ public function getDecimalTypeDeclarationSQL(array $column) { return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column); } /** * Get unsigned declaration for a column. * * @param mixed[] $columnDef * * @return string */ private function getUnsignedDeclaration(array $columnDef) { return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $autoinc = ''; if (! empty($column['autoincrement'])) { $autoinc = ' AUTO_INCREMENT'; } return $this->getUnsignedDeclaration($column) . $autoinc; } /** * {@inheritDoc} */ public function getColumnCharsetDeclarationSQL($charset) { return 'CHARACTER SET ' . $charset; } /** * {@inheritDoc} */ public function getColumnCollationDeclarationSQL($collation) { return 'COLLATE ' . $this->quoteSingleIdentifier($collation); } /** * {@inheritDoc} */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); return $query; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { $indexName = $index->getQuotedName($this); } elseif (is_string($index)) { $indexName = $index; } else { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' ); } if ($table instanceof Table) { $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' ); } if ($index instanceof Index && $index->isPrimary()) { // mysql primary keys are always named "PRIMARY", // so we cannot use them in statements because of them being keyword. return $this->getDropPrimaryKeySQL($table); } return 'DROP INDEX ' . $indexName . ' ON ' . $table; } /** * @param string $table * * @return string */ protected function getDropPrimaryKeySQL($table) { return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getName() { return 'mysql'; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'LOCK IN SHARE MODE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'tinyint' => 'boolean', 'smallint' => 'smallint', 'mediumint' => 'integer', 'int' => 'integer', 'integer' => 'integer', 'bigint' => 'bigint', 'tinytext' => 'text', 'mediumtext' => 'text', 'longtext' => 'text', 'text' => 'text', 'varchar' => 'string', 'string' => 'string', 'char' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'time' => 'time', 'float' => 'float', 'double' => 'float', 'real' => 'float', 'decimal' => 'decimal', 'numeric' => 'decimal', 'year' => 'date', 'longblob' => 'blob', 'blob' => 'blob', 'mediumblob' => 'blob', 'tinyblob' => 'blob', 'binary' => 'binary', 'varbinary' => 'binary', 'set' => 'simple_array', ]; } /** * {@inheritDoc} */ public function getVarcharMaxLength() { return 65535; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 65535; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\MySQLKeywords::class; } /** * {@inheritDoc} * * MySQL commits a transaction implicitly when DROP TABLE is executed, however not * if DROP TEMPORARY TABLE is executed. */ public function getDropTemporaryTableSQL($table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } elseif (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' ); } return 'DROP TEMPORARY TABLE ' . $table; } /** * Gets the SQL Snippet used to declare a BLOB column type. * TINYBLOB : 2 ^ 8 - 1 = 255 * BLOB : 2 ^ 16 - 1 = 65535 * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 * LONGBLOB : 2 ^ 32 - 1 = 4294967295 * * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { if (! empty($column['length']) && is_numeric($column['length'])) { $length = $column['length']; if ($length <= static::LENGTH_LIMIT_TINYBLOB) { return 'TINYBLOB'; } if ($length <= static::LENGTH_LIMIT_BLOB) { return 'BLOB'; } if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { return 'MEDIUMBLOB'; } } return 'LONGBLOB'; } /** * {@inheritdoc} */ public function quoteStringLiteral($str) { $str = str_replace('\\', '\\\\', $str); // MySQL requires backslashes to be escaped aswell. return parent::quoteStringLiteral($str); } /** * {@inheritdoc} */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::REPEATABLE_READ; } public function supportsColumnLengthIndexes(): bool { return true; } } dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php 0000644 00000105403 15120025741 0016057 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types\BinaryType; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function count; use function explode; use function func_get_arg; use function func_num_args; use function implode; use function preg_match; use function sprintf; use function strlen; use function strpos; use function strtoupper; use function substr; /** * OraclePlatform. */ class OraclePlatform extends AbstractPlatform { /** * Assertion for Oracle identifiers. * * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm * * @param string $identifier * * @return void * * @throws Exception */ public static function assertValidIdentifier($identifier) { if (! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) { throw new Exception('Invalid Oracle identifier'); } } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return sprintf('SUBSTR(%s, %d, %d)', $string, $start, $length); } return sprintf('SUBSTR(%s, %d)', $string, $start); } /** * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { switch ($type) { case 'date': case 'time': case 'timestamp': default: return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; } } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'INSTR(' . $str . ', ' . $substr . ')'; } return 'INSTR(' . $str . ', ' . $substr . ', ' . $startPos . ')'; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'SYS_GUID()'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::MONTH: case DateIntervalUnit::QUARTER: case DateIntervalUnit::YEAR: switch ($unit) { case DateIntervalUnit::QUARTER: $interval *= 3; break; case DateIntervalUnit::YEAR: $interval *= 12; break; } return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; default: $calculationClause = ''; switch ($unit) { case DateIntervalUnit::SECOND: $calculationClause = '/24/60/60'; break; case DateIntervalUnit::MINUTE: $calculationClause = '/24/60'; break; case DateIntervalUnit::HOUR: $calculationClause = '/24'; break; case DateIntervalUnit::WEEK: $calculationClause = '*7'; break; } return '(' . $date . $operator . $interval . $calculationClause . ')'; } } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2); } /** * {@inheritDoc} */ public function getBitAndComparisonExpression($value1, $value2) { return 'BITAND(' . $value1 . ', ' . $value2 . ')'; } /** * {@inheritDoc} */ public function getBitOrComparisonExpression($value1, $value2) { return '(' . $value1 . '-' . $this->getBitAndComparisonExpression($value1, $value2) . '+' . $value2 . ')'; } /** * {@inheritDoc} * * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection * in {@see listSequences()} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' MINVALUE ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences * * @return string */ private function getSequenceCacheSQL(Sequence $sequence) { if ($sequence->getCache() === 0) { return ' NOCACHE'; } if ($sequence->getCache() === 1) { return ' NOCACHE'; } if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT ' . $sequence . '.nextval FROM DUAL'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case TransactionIsolationLevel::READ_COMMITTED: return 'READ COMMITTED'; case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return 'SERIALIZABLE'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'NUMBER(1)'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'NUMBER(10)'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'NUMBER(20)'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'NUMBER(5)'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)') : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'RAW(' . ($length ?: $this->getBinaryMaxLength()) . ')'; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 2000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * {@inheritDoc} */ public function getListDatabasesSQL() { return 'SELECT username FROM all_users'; } /** * {@inheritDoc} */ public function getListSequencesSQL($database) { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); return 'SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ' . 'WHERE SEQUENCE_OWNER = ' . $database; } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $indexes = $options['indexes'] ?? []; $options['indexes'] = []; $sql = parent::_getCreateTableSQL($name, $columns, $options); foreach ($columns as $columnName => $column) { if (isset($column['sequence'])) { $sql[] = $this->getCreateSequenceSQL($column['sequence']); } if ( ! isset($column['autoincrement']) || ! $column['autoincrement'] && (! isset($column['autoinc']) || ! $column['autoinc']) ) { continue; } $sql = array_merge($sql, $this->getCreateAutoincrementSql($columnName, $name)); } if (isset($indexes) && ! empty($indexes)) { foreach ($indexes as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } return $sql; } /** * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html */ public function getListTableIndexesSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT uind_col.index_name AS name, ( SELECT uind.index_type FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ) AS type, decode( ( SELECT uind.uniqueness FROM user_indexes uind WHERE uind.index_name = uind_col.index_name ), 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, uind_col.column_name AS column_name, uind_col.column_position AS column_pos, ( SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.index_name = uind_col.index_name ) AS is_primary FROM user_ind_columns uind_col WHERE uind_col.table_name = " . $table . ' ORDER BY uind_col.column_position ASC'; } /** * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT * FROM sys.user_tables'; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { return 'SELECT view_name, text FROM sys.user_views'; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * @param string $name * @param string $table * @param int $start * * @return string[] */ public function getCreateAutoincrementSql($name, $table, $start = 1) { $tableIdentifier = $this->normalizeIdentifier($table); $quotedTableName = $tableIdentifier->getQuotedName($this); $unquotedTableName = $tableIdentifier->getName(); $nameIdentifier = $this->normalizeIdentifier($name); $quotedName = $nameIdentifier->getQuotedName($this); $unquotedName = $nameIdentifier->getName(); $sql = []; $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true); $sql[] = "DECLARE constraints_Count NUMBER; BEGIN SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = '" . $unquotedTableName . "' AND CONSTRAINT_TYPE = 'P'; IF constraints_Count = 0 OR constraints_Count = '' THEN EXECUTE IMMEDIATE '" . $this->getCreateConstraintSQL($idx, $quotedTableName) . "'; END IF; END;"; $sequenceName = $this->getIdentitySequenceName( $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, $nameIdentifier->isQuoted() ? $quotedName : $unquotedName ); $sequence = new Sequence($sequenceName, $start); $sql[] = $this->getCreateSequenceSQL($sequence); $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' BEFORE INSERT ON ' . $quotedTableName . ' FOR EACH ROW DECLARE last_Sequence NUMBER; last_InsertID NUMBER; BEGIN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; ELSE SELECT NVL(Last_Number, 0) INTO last_Sequence FROM User_Sequences WHERE Sequence_Name = \'' . $sequence->getName() . '\'; SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; WHILE (last_InsertID > last_Sequence) LOOP SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END LOOP; END IF; END;'; return $sql; } /** * Returns the SQL statements to drop the autoincrement for the given table name. * * @param string $table The table name to drop the autoincrement for. * * @return string[] */ public function getDropAutoincrementSql($table) { $table = $this->normalizeIdentifier($table); $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); $identitySequenceName = $this->getIdentitySequenceName( $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), '' ); return [ 'DROP TRIGGER ' . $autoincrementIdentifierName, $this->getDropSequenceSQL($identitySequenceName), $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), ]; } /** * Normalizes the given identifier. * * Uppercases the given identifier if it is not quoted by intention * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. * * @param string $name The identifier to normalize. * * @return Identifier The normalized identifier. */ private function normalizeIdentifier($name) { $identifier = new Identifier($name); return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); } /** * Adds suffix to identifier, * * if the new string exceeds max identifier length, * keeps $suffix, cuts from $identifier as much as the part exceeding. */ private function addSuffix(string $identifier, string $suffix): string { $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix); if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) { $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix); } return $identifier . $suffix; } /** * Returns the autoincrement primary key identifier name for the given table identifier. * * Quotes the autoincrement primary key identifier name * if the given table name is quoted by intention. * * @param Identifier $table The table identifier to return the autoincrement primary key identifier name for. * * @return string */ private function getAutoincrementIdentifierName(Identifier $table) { $identifierName = $this->addSuffix($table->getName(), '_AI_PK'); return $table->isQuoted() ? $this->quoteSingleIdentifier($identifierName) : $identifierName; } /** * {@inheritDoc} */ public function getListTableForeignKeysSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return "SELECT alc.constraint_name, alc.DELETE_RULE, cols.column_name \"local_column\", cols.position, ( SELECT r_cols.table_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"references_table\", ( SELECT r_cols.column_name FROM user_cons_columns r_cols WHERE alc.r_constraint_name = r_cols.constraint_name AND r_cols.position = cols.position ) AS \"foreign_column\" FROM user_cons_columns cols JOIN user_constraints alc ON alc.constraint_name = cols.constraint_name AND alc.constraint_type = 'R' AND alc.table_name = " . $table . ' ORDER BY cols.constraint_name ASC, cols.position ASC'; } /** * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); return 'SELECT * FROM user_constraints WHERE table_name = ' . $table; } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = $this->normalizeIdentifier($table); $table = $this->quoteStringLiteral($table->getName()); $tabColumnsTableName = 'user_tab_columns'; $colCommentsTableName = 'user_col_comments'; $tabColumnsOwnerCondition = ''; $colCommentsOwnerCondition = ''; if ($database !== null && $database !== '/') { $database = $this->normalizeIdentifier($database); $database = $this->quoteStringLiteral($database->getName()); $tabColumnsTableName = 'all_tab_columns'; $colCommentsTableName = 'all_col_comments'; $tabColumnsOwnerCondition = ' AND c.owner = ' . $database; $colCommentsOwnerCondition = ' AND d.OWNER = c.OWNER'; } return sprintf( <<<'SQL' SELECT c.*, ( SELECT d.comments FROM %s d WHERE d.TABLE_NAME = c.TABLE_NAME%s AND d.COLUMN_NAME = c.COLUMN_NAME ) AS comments FROM %s c WHERE c.table_name = %s%s ORDER BY c.column_id SQL , $colCommentsTableName, $colCommentsOwnerCondition, $tabColumnsTableName, $table, $tabColumnsOwnerCondition ); } /** * {@inheritDoc} */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof Sequence) { $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if (! $foreignKey instanceof ForeignKeyConstraint) { $foreignKey = new Identifier($foreignKey); } if (! $table instanceof Table) { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritdoc} */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $referentialAction = null; if ($foreignKey->hasOption('onDelete')) { $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } return $referentialAction ? ' ON DELETE ' . $referentialAction : ''; } /** * {@inheritdoc} */ public function getForeignKeyReferentialActionSQL($action) { $action = strtoupper($action); switch ($action) { case 'RESTRICT': // RESTRICT is not supported, therefore falling back to NO ACTION. case 'NO ACTION': // NO ACTION cannot be declared explicitly, // therefore returning empty string to indicate to OMIT the referential clause. return ''; case 'CASCADE': case 'SET NULL': return $action; default: // SET DEFAULT is not supported, throw exception instead. throw new InvalidArgumentException('Invalid foreign key action: ' . $action); } } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP USER ' . $name . ' CASCADE'; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; $fields = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); $comment = $this->getColumnComment($column); if (! $comment) { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $comment ); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ADD (' . implode(', ', $fields) . ')'; } $fields = []; foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $column = $columnDiff->column; // Do not generate column alteration clause if type is binary and only fixed property has changed. // Oracle only supports binary type columns with variable length. // Avoids unnecessary table alteration statements. if ( $column->getType() instanceof BinaryType && $columnDiff->hasChanged('fixed') && count($columnDiff->changedProperties) === 1 ) { continue; } $columnHasChangedComment = $columnDiff->hasChanged('comment'); /** * Do not add query part if only comment has changed */ if (! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { $columnInfo = $column->toArray(); if (! $columnDiff->hasChanged('notnull')) { unset($columnInfo['notnull']); } $fields[] = $column->getQuotedName($this) . $this->getColumnDeclarationSQL('', $columnInfo); } if (! $columnHasChangedComment) { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $this->getColumnComment($column) ); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' MODIFY (' . implode(', ', $fields) . ')'; } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $fields = []; foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $fields[] = $column->getQuotedName($this); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' DROP (' . implode(', ', $fields) . ')'; } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $diff->getName($this)->getQuotedName($this), $newName->getQuotedName($this) ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * {@inheritdoc} */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $default = $this->getDefaultValueDeclarationSQL($column); $notnull = ''; if (isset($column['notnull'])) { $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL'; } $unique = isset($column['unique']) && $column['unique'] ? ' ' . $this->getUniqueFieldDeclarationSQL() : ''; $check = isset($column['check']) && $column['check'] ? ' ' . $column['check'] : ''; $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $default . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritDoc} * * @deprecated */ public function prefersSequences() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::prefersSequences() is deprecated without replacement and removed in DBAL 3.0' ); return true; } /** * {@inheritdoc} */ public function usesSequenceEmulatedIdentityColumns() { return true; } /** * {@inheritdoc} */ public function getIdentitySequenceName($tableName, $columnName) { $table = new Identifier($tableName); // No usage of column name to preserve BC compatibility with <2.5 $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ'); if ($table->isQuoted()) { $identitySequenceName = '"' . $identitySequenceName . '"'; } $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); return $identitySequenceIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} */ public function getName() { return 'oracle'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset = null) { if ($limit === null && $offset <= 0) { return $query; } if (preg_match('/^\s*SELECT/i', $query)) { if (! preg_match('/\sFROM\s/i', $query)) { $query .= ' FROM dual'; } $columns = ['a.*']; if ($offset > 0) { $columns[] = 'ROWNUM AS doctrine_rownum'; } $query = sprintf('SELECT %s FROM (%s) a', implode(', ', $columns), $query); if ($limit !== null) { $query .= sprintf(' WHERE ROWNUM <= %d', $offset + $limit); } if ($offset > 0) { $query = sprintf('SELECT * FROM (%s) WHERE doctrine_rownum >= %d', $query, $offset + 1); } } return $query; } /** * {@inheritDoc} * * Oracle returns all column names in SQL result sets in uppercase. * * @deprecated */ public function getSQLResultCasing($column) { return strtoupper($column); } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE GLOBAL TEMPORARY TABLE'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sP'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d 00:00:00'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return '1900-01-01 H:i:s'; } /** * {@inheritDoc} * * @deprecated */ public function fixSchemaElementName($schemaElementName) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4132', 'AbstractPlatform::fixSchemaElementName is deprecated with no replacement and removed in DBAL 3.0' ); if (strlen($schemaElementName) > 30) { // Trim it return substr($schemaElementName, 0, 30); } return $schemaElementName; } /** * {@inheritDoc} */ public function getMaxIdentifierLength() { return 30; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} * * @deprecated */ public function supportsForeignKeyOnUpdate() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::supportsForeignKeyOnUpdate() is deprecated without replacement and removed in DBAL 3.0' ); return false; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getDummySelectSQL() { $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; return sprintf('SELECT %s FROM DUAL', $expression); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'integer' => 'integer', 'number' => 'integer', 'pls_integer' => 'boolean', 'binary_integer' => 'boolean', 'varchar' => 'string', 'varchar2' => 'string', 'nvarchar2' => 'string', 'char' => 'string', 'nchar' => 'string', 'date' => 'date', 'timestamp' => 'datetime', 'timestamptz' => 'datetimetz', 'float' => 'float', 'binary_float' => 'float', 'binary_double' => 'float', 'long' => 'string', 'clob' => 'text', 'nclob' => 'text', 'raw' => 'binary', 'long raw' => 'blob', 'rowid' => 'string', 'urowid' => 'string', 'blob' => 'blob', ]; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\OracleKeywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } public function getListTableCommentsSQL(string $table, ?string $database = null): string { $tableCommentsName = 'user_tab_comments'; $ownerCondition = ''; if ($database !== null && $database !== '/') { $tableCommentsName = 'all_tab_comments'; $ownerCondition = ' AND owner = ' . $this->quoteStringLiteral( $this->normalizeIdentifier($database)->getName() ); } return sprintf( <<<'SQL' SELECT comments FROM %s WHERE table_name = %s%s SQL , $tableCommentsName, $this->quoteStringLiteral($this->normalizeIdentifier($table)->getName()), $ownerCondition ); } } dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL100Platform.php 0000644 00000001724 15120025741 0017077 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 10.0 database platform. */ class PostgreSQL100Platform extends PostgreSQL94Platform { protected function getReservedKeywordsClass(): string { return PostgreSQL100Keywords::class; } /** * {@inheritDoc} */ public function getListSequencesSQL($database): string { return 'SELECT sequence_name AS relname, sequence_schema AS schemaname, minimum_value AS min_value, increment AS increment_by FROM information_schema.sequences WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . " AND sequence_schema NOT LIKE 'pg\_%' AND sequence_schema != 'information_schema'"; } } dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php 0000644 00000002205 15120025741 0017023 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use function explode; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 9.1 database platform. * * @deprecated Use PostgreSQL 9.4 or newer */ class PostgreSQL91Platform extends PostgreSqlPlatform { /** * {@inheritDoc} */ public function supportsColumnCollation() { return true; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\PostgreSQL91Keywords::class; } /** * {@inheritDoc} */ public function getColumnCollationDeclarationSQL($collation) { return 'COLLATE ' . $this->quoteSingleIdentifier($collation); } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $sql = parent::getListTableColumnsSQL($table, $database); $parts = explode('AS complete_type,', $sql, 2); return $parts[0] . 'AS complete_type, ' . '(SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,' . $parts[1]; } } dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php 0000644 00000002707 15120025741 0017033 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\Types; use function sprintf; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 9.2 database platform. * * @deprecated Use PostgreSQL 9.4 or newer */ class PostgreSQL92Platform extends PostgreSQL91Platform { /** * {@inheritdoc} */ public function getJsonTypeDeclarationSQL(array $column) { return 'JSON'; } /** * {@inheritdoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SMALLSERIAL'; } return parent::getSmallIntTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function hasNativeJsonType() { return true; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\PostgreSQL92Keywords::class; } /** * {@inheritdoc} */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = Types::JSON; } /** * {@inheritdoc} */ public function getCloseActiveDatabaseConnectionsSQL($database) { return sprintf( 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = %s', $this->quoteStringLiteral($database) ); } } dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL94Platform.php 0000644 00000001461 15120025741 0017031 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Types\Types; /** * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4 database platform. */ class PostgreSQL94Platform extends PostgreSQL92Platform { /** * {@inheritdoc} */ public function getJsonTypeDeclarationSQL(array $column) { if (! empty($column['jsonb'])) { return 'JSONB'; } return 'JSON'; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\PostgreSQL94Keywords::class; } /** * {@inheritdoc} */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['jsonb'] = Types::JSON; } } dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php 0000644 00000111201 15120025741 0016746 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Types\BigIntType; use Doctrine\DBAL\Types\BinaryType; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\IntegerType; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use UnexpectedValueException; use function array_diff; use function array_merge; use function array_unique; use function array_values; use function count; use function explode; use function implode; use function in_array; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function sprintf; use function strpos; use function strtolower; use function trim; /** * PostgreSqlPlatform. * * @deprecated Use PostgreSQL 9.4 or newer * * @todo Rename: PostgreSQLPlatform */ class PostgreSqlPlatform extends AbstractPlatform { /** @var bool */ private $useBooleanTrueFalseStrings = true; /** @var string[][] PostgreSQL booleans literals */ private $booleanLiterals = [ 'true' => [ 't', 'true', 'y', 'yes', 'on', '1', ], 'false' => [ 'f', 'false', 'n', 'no', 'off', '0', ], ]; /** * PostgreSQL has different behavior with some drivers * with regard to how booleans have to be handled. * * Enables use of 'true'/'false' or otherwise 1 and 0 instead. * * @param bool $flag * * @return void */ public function setUseBooleanTrueFalseStrings($flag) { $this->useBooleanTrueFalseStrings = (bool) $flag; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; } return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; } /** * {@inheritDoc} */ public function getNowExpression() { return 'LOCALTIMESTAMP(0)'; } /** * {@inheritDoc} */ public function getRegexpExpression() { return 'SIMILAR TO'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos !== false) { $str = $this->getSubstringExpression($str, $startPos); return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0' . ' ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . ($startPos - 1) . ') END'; } return 'POSITION(' . $substr . ' IN ' . $str . ')'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { if ($unit === DateIntervalUnit::QUARTER) { $interval *= 3; $unit = DateIntervalUnit::MONTH; } return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; } /** * {@inheritDoc} */ public function supportsSequences() { return true; } /** * {@inheritDoc} */ public function supportsSchemas() { return true; } /** * {@inheritdoc} */ public function getDefaultSchemaName() { return 'public'; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritdoc} */ public function supportsPartialIndexes() { return true; } /** * {@inheritdoc} */ public function usesSequenceEmulatedIdentityColumns() { return true; } /** * {@inheritdoc} */ public function getIdentitySequenceName($tableName, $columnName) { return $tableName . '_' . $columnName . '_seq'; } /** * {@inheritDoc} */ public function supportsCommentOnStatement() { return true; } /** * {@inheritDoc} * * @deprecated */ public function prefersSequences() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4229', 'AbstractPlatform::prefersSequences() is deprecated without replacement and removed in DBAL 3.0' ); return true; } /** * {@inheritDoc} */ public function hasNativeGuidType() { return true; } /** * {@inheritDoc} */ public function getListDatabasesSQL() { return 'SELECT datname FROM pg_database'; } /** * {@inheritDoc} */ public function getListNamespacesSQL() { return "SELECT schema_name AS nspname FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg\_%' AND schema_name != 'information_schema'"; } /** * {@inheritDoc} */ public function getListSequencesSQL($database) { return "SELECT sequence_name AS relname, sequence_schema AS schemaname FROM information_schema.sequences WHERE sequence_schema NOT LIKE 'pg\_%' AND sequence_schema != 'information_schema'"; } /** * {@inheritDoc} */ public function getListTablesSQL() { return "SELECT quote_ident(table_name) AS table_name, table_schema AS schema_name FROM information_schema.tables WHERE table_schema NOT LIKE 'pg\_%' AND table_schema != 'information_schema' AND table_name != 'geometry_columns' AND table_name != 'spatial_ref_sys' AND table_type != 'VIEW'"; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { return 'SELECT quote_ident(table_name) AS viewname, table_schema AS schemaname, view_definition AS definition FROM information_schema.views WHERE view_definition IS NOT NULL'; } /** * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = ( SELECT c.oid FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace ) AND r.contype = 'f'"; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return sprintf( <<<'SQL' SELECT quote_ident(relname) as relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname = %s AND pg_class.oid = pg_index.indrelid AND (indisunique = 't' OR indisprimary = 't') ) SQL , $table ); } /** * {@inheritDoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ public function getListTableIndexesSQL($table, $database = null) { return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, pg_index.indkey, pg_index.indrelid, pg_get_expr(indpred, indrelid) AS where FROM pg_class, pg_index WHERE oid IN ( SELECT indexrelid FROM pg_index si, pg_class sc, pg_namespace sn WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid ) AND pg_index.indexrelid = oid'; } /** * @param string $table * @param string $classAlias * @param string $namespaceAlias * * @return string */ private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n') { $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); } else { $schema = 'ANY(current_schemas(false))'; } $table = new Identifier($table); $table = $this->quoteStringLiteral($table->getName()); return $whereClause . sprintf( '%s.relname = %s AND %s.nspname = %s', $classAlias, $table, $namespaceAlias, $schema ); } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT a.attnum, quote_ident(a.attname) AS field, t.typname AS type, format_type(a.atttypid, a.atttypmod) AS complete_type, (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, a.attnotnull AS isnotnull, (SELECT 't' FROM pg_index WHERE c.oid = pg_index.indrelid AND pg_index.indkey[0] = a.attnum AND pg_index.indisprimary = 't' ) AS pri, (SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum ) AS default, (SELECT pg_description.description FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid ) AS comment FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n WHERE " . $this->getTableWhereClause($table, 'c', 'n') . ' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND n.oid = c.relnamespace ORDER BY a.attnum'; } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * Returns the SQL statement for disallowing new connections on the given database. * * This is useful to force DROP DATABASE operations which could fail because of active connections. * * @deprecated * * @param string $database The name of the database to disallow new connections for. * * @return string */ public function getDisallowDatabaseConnectionsSQL($database) { return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = " . $this->quoteStringLiteral($database); } /** * Returns the SQL statement for closing currently active connections on the given database. * * This is useful to force DROP DATABASE operations which could fail because of active connections. * * @deprecated * * @param string $database The name of the database to close currently active connections for. * * @return string */ public function getCloseActiveDatabaseConnectionsSQL($database) { return 'SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = ' . $this->quoteStringLiteral($database); } /** * {@inheritDoc} */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { $query .= ' DEFERRABLE'; } else { $query .= ' NOT DEFERRABLE'; } if ( ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ) { $query .= ' INITIALLY DEFERRED'; } else { $query .= ' INITIALLY IMMEDIATE'; } return $query; } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $commentsSQL = []; $columnSql = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $comment ); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $query = 'DROP ' . $column->getQuotedName($this); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if ($this->isUnchangedBinaryColumn($columnDiff)) { continue; } $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this); $column = $columnDiff->column; if ( $columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed') ) { $type = $column->getType(); // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type $columnDefinition = $column->toArray(); $columnDefinition['autoincrement'] = false; // here was a server version check before, but DBAL API does not support this anymore. $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) { $defaultClause = $column->getDefault() === null ? ' DROP DEFAULT' : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray()); $query = 'ALTER ' . $oldColumnName . $defaultClause; $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } if ($columnDiff->hasChanged('notnull')) { $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } if ($columnDiff->hasChanged('autoincrement')) { if ($column->getAutoincrement()) { // add autoincrement $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName); $sql[] = 'CREATE SEQUENCE ' . $seqName; $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $diff->getName($this)->getQuotedName($this) . '))'; $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } else { // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } } $newComment = $this->getColumnComment($column); $oldComment = $this->getOldColumnComment($columnDiff); if ( $columnDiff->hasChanged('comment') || ($columnDiff->fromColumn !== null && $oldComment !== $newComment) ) { $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $newComment ); } if (! $columnDiff->hasChanged('length')) { continue; } $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $diff->getName($this)->getQuotedName($this), $newName->getQuotedName($this) ); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * Checks whether a given column diff is a logically unchanged binary type column. * * Used to determine whether a column alteration for a binary type column can be skipped. * Doctrine's {@link BinaryType} and {@link BlobType} are mapped to the same database column type on this platform * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator * might detect differences for binary type columns which do not have to be propagated * to database as there actually is no difference at database level. * * @param ColumnDiff $columnDiff The column diff to check against. * * @return bool True if the given column diff is an unchanged binary type column, false otherwise. */ private function isUnchangedBinaryColumn(ColumnDiff $columnDiff) { $columnType = $columnDiff->column->getType(); if (! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) { return false; } $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null; if ($fromColumn) { $fromColumnType = $fromColumn->getType(); if (! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) { return false; } return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; } if ($columnDiff->hasChanged('type')) { return false; } return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { if (strpos($tableName, '.') !== false) { [$schema] = explode('.', $tableName); $oldIndexName = $schema . '.' . $oldIndexName; } return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritdoc} */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $comment ); } /** * {@inheritDoc} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue() . ' START ' . $sequence->getInitialValue() . $this->getSequenceCacheSQL($sequence); } /** * {@inheritDoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . $this->getSequenceCacheSQL($sequence); } /** * Cache definition for sequences * * @return string */ private function getSequenceCacheSQL(Sequence $sequence) { if ($sequence->getCache() > 1) { return ' CACHE ' . $sequence->getCache(); } return ''; } /** * {@inheritDoc} */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof Sequence) { $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence . ' CASCADE'; } /** * {@inheritDoc} */ public function getCreateSchemaSQL($schemaName) { return 'CREATE SCHEMA ' . $schemaName; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { return $this->getDropConstraintSQL($foreignKey, $table); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return $sql; } /** * Converts a single boolean value. * * First converts the value to its native PHP boolean type * and passes it to the given callback function to be reconverted * into any custom representation. * * @param mixed $value The value to convert. * @param callable $callback The callback function to use for converting the real boolean value. * * @return mixed * * @throws UnexpectedValueException */ private function convertSingleBooleanValue($value, $callback) { if ($value === null) { return $callback(null); } if (is_bool($value) || is_numeric($value)) { return $callback((bool) $value); } if (! is_string($value)) { return $callback(true); } /** * Better safe than sorry: http://php.net/in_array#106319 */ if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { return $callback(false); } if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { return $callback(true); } throw new UnexpectedValueException("Unrecognized boolean literal '${value}'"); } /** * Converts one or multiple boolean values. * * First converts the value(s) to their native PHP boolean type * and passes them to the given callback function to be reconverted * into any custom representation. * * @param mixed $item The value(s) to convert. * @param callable $callback The callback function to use for converting the real boolean value(s). * * @return mixed */ private function doConvertBooleans($item, $callback) { if (is_array($item)) { foreach ($item as $key => $value) { $item[$key] = $this->convertSingleBooleanValue($value, $callback); } return $item; } return $this->convertSingleBooleanValue($item, $callback); } /** * {@inheritDoc} * * Postgres wants boolean values converted to the strings 'true'/'false'. */ public function convertBooleans($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleans($item); } return $this->doConvertBooleans( $item, /** * @param mixed $value */ static function ($value) { if ($value === null) { return 'NULL'; } return $value === true ? 'true' : 'false'; } ); } /** * {@inheritDoc} */ public function convertBooleansToDatabaseValue($item) { if (! $this->useBooleanTrueFalseStrings) { return parent::convertBooleansToDatabaseValue($item); } return $this->doConvertBooleans( $item, /** * @param mixed $value */ static function ($value) { return $value === null ? null : (int) $value; } ); } /** * {@inheritDoc} */ public function convertFromBoolean($item) { if ($item !== null && in_array(strtolower($item), $this->booleanLiterals['false'], true)) { return false; } return parent::convertFromBoolean($item); } /** * {@inheritDoc} */ public function getSequenceNextValSQL($sequence) { return "SELECT NEXTVAL('" . $sequence . "')"; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'SERIAL'; } return 'INT'; } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { if (! empty($column['autoincrement'])) { return 'BIGSERIAL'; } return 'BIGINT'; } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT'; } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UUID'; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0) WITHOUT TIME ZONE'; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'UUID_GENERATE_V4()'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ''; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BYTEA'; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'TEXT'; } /** * {@inheritDoc} */ public function getName() { return 'postgresql'; } /** * {@inheritDoc} * * PostgreSQL returns all column names in SQL result sets in lowercase. * * @deprecated */ public function getSQLResultCasing($column) { return strtolower($column); } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sO'; } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); if ($cascade) { $sql .= ' CASCADE'; } return $sql; } /** * {@inheritDoc} */ public function getReadLockSQL() { return 'FOR SHARE'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'smallint' => 'smallint', 'int2' => 'smallint', 'serial' => 'integer', 'serial4' => 'integer', 'int' => 'integer', 'int4' => 'integer', 'integer' => 'integer', 'bigserial' => 'bigint', 'serial8' => 'bigint', 'bigint' => 'bigint', 'int8' => 'bigint', 'bool' => 'boolean', 'boolean' => 'boolean', 'text' => 'text', 'tsvector' => 'text', 'varchar' => 'string', 'interval' => 'string', '_varchar' => 'string', 'char' => 'string', 'bpchar' => 'string', 'inet' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'timestamptz' => 'datetimetz', 'time' => 'time', 'timetz' => 'time', 'float' => 'float', 'float4' => 'float', 'float8' => 'float', 'double' => 'float', 'double precision' => 'float', 'real' => 'float', 'decimal' => 'decimal', 'money' => 'decimal', 'numeric' => 'decimal', 'year' => 'date', 'uuid' => 'guid', 'bytea' => 'blob', ]; } /** * {@inheritDoc} */ public function getVarcharMaxLength() { return 65535; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 0; } /** * {@inheritdoc} */ public function getBinaryDefaultLength() { return 0; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\PostgreSQLKeywords::class; } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BYTEA'; } /** * {@inheritdoc} */ public function getDefaultValueDeclarationSQL($column) { if ($this->isSerialColumn($column)) { return ''; } return parent::getDefaultValueDeclarationSQL($column); } /** * @param mixed[] $column */ private function isSerialColumn(array $column): bool { return isset($column['type'], $column['autoincrement']) && $column['autoincrement'] === true && $this->isNumericType($column['type']); } /** * Check whether the type of a column is changed in a way that invalidates the default value for the column */ private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool { if (! $columnDiff->fromColumn) { return $columnDiff->hasChanged('type'); } $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType()); $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType()); // default should not be changed when switching between numeric types and the default comes from a sequence return $columnDiff->hasChanged('type') && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement()); } private function isNumericType(Type $type): bool { return $type instanceof IntegerType || $type instanceof BigIntType; } private function getOldColumnComment(ColumnDiff $columnDiff): ?string { return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null; } public function getListTableMetadataSQL(string $table, ?string $schema = null): string { if ($schema !== null) { $table = $schema . '.' . $table; } return sprintf( <<<'SQL' SELECT obj_description(%s::regclass) AS table_comment; SQL , $this->quoteStringLiteral($table) ); } } dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php 0000644 00000001104 15120025741 0017147 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * The SQLAnywhere11Platform provides the behavior, features and SQL dialect of the * SAP Sybase SQL Anywhere 11 database platform. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywhere11Platform extends SQLAnywherePlatform { /** * {@inheritdoc} */ public function getRegexpExpression() { return 'REGEXP'; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLAnywhere11Keywords::class; } } dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php 0000644 00000005313 15120025741 0017156 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Sequence; /** * The SQLAnywhere12Platform provides the behavior, features and SQL dialect of the * SAP Sybase SQL Anywhere 12 database platform. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywhere12Platform extends SQLAnywhere11Platform { /** * {@inheritdoc} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' START WITH ' . $sequence->getInitialValue() . ' MINVALUE ' . $sequence->getInitialValue(); } /** * {@inheritdoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize(); } /** * {@inheritdoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s.uP'; } /** * {@inheritdoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'TIMESTAMP WITH TIME ZONE'; } /** * {@inheritdoc} */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof Sequence) { $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence; } /** * {@inheritdoc} */ public function getListSequencesSQL($database) { return 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE'; } /** * {@inheritdoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT ' . $sequence . '.NEXTVAL'; } /** * {@inheritdoc} */ public function supportsSequences() { return true; } /** * {@inheritdoc} */ protected function getAdvancedIndexOptionsSQL(Index $index) { if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) { return ' WITH NULLS NOT DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); } return parent::getAdvancedIndexOptionsSQL($index); } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLAnywhere12Keywords::class; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['timestamp with time zone'] = 'datetime'; } } dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php 0000644 00000002246 15120025741 0017164 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Index; use UnexpectedValueException; /** * The SQLAnywhere16Platform provides the behavior, features and SQL dialect of the * SAP Sybase SQL Anywhere 16 database platform. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywhere16Platform extends SQLAnywhere12Platform { /** * {@inheritdoc} */ protected function getAdvancedIndexOptionsSQL(Index $index) { if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) { throw new UnexpectedValueException( 'An Index can either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.' ); } if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) { return ' WITH NULLS DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); } return parent::getAdvancedIndexOptionsSQL($index); } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLAnywhere16Keywords::class; } } dbal/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php 0000644 00000124236 15120025741 0017021 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\Deprecations\Deprecation; use InvalidArgumentException; use function array_merge; use function array_unique; use function array_values; use function assert; use function count; use function explode; use function func_get_args; use function get_class; use function implode; use function is_string; use function preg_match; use function sprintf; use function strlen; use function strpos; use function strtoupper; use function substr; /** * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the * SAP Sybase SQL Anywhere 10 database platform. * * @deprecated Support for SQLAnywhere will be removed in 3.0. */ class SQLAnywherePlatform extends AbstractPlatform { public const FOREIGN_KEY_MATCH_SIMPLE = 1; public const FOREIGN_KEY_MATCH_FULL = 2; public const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129; public const FOREIGN_KEY_MATCH_FULL_UNIQUE = 130; /** * {@inheritdoc} */ public function appendLockHint($fromClause, $lockMode) { switch (true) { case $lockMode === LockMode::NONE: return $fromClause; case $lockMode === LockMode::PESSIMISTIC_READ: return $fromClause . ' WITH (UPDLOCK)'; case $lockMode === LockMode::PESSIMISTIC_WRITE: return $fromClause . ' WITH (XLOCK)'; default: return $fromClause; } } /** * {@inheritdoc} * * SQL Anywhere supports a maximum length of 128 bytes for identifiers. */ public function fixSchemaElementName($schemaElementName) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4132', 'AbstractPlatform::fixSchemaElementName is deprecated with no replacement and removed in DBAL 3.0' ); $maxIdentifierLength = $this->getMaxIdentifierLength(); if (strlen($schemaElementName) > $maxIdentifierLength) { return substr($schemaElementName, 0, $maxIdentifierLength); } return $schemaElementName; } /** * {@inheritdoc} */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match')); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if ($foreignKey->hasOption('check_on_commit') && (bool) $foreignKey->getOption('check_on_commit')) { $query .= ' CHECK ON COMMIT'; } if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) { $query .= ' CLUSTERED'; } if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) { $query .= ' FOR OLAP WORKLOAD'; } return $query; } /** * {@inheritdoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = []; $columnSql = []; $commentsSQL = []; $tableSql = []; $alterClauses = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $alterClauses[] = $this->getAlterTableAddColumnClause($column); $comment = $this->getColumnComment($column); if ($comment === null || $comment === '') { continue; } $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $comment ); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $alterClauses[] = $this->getAlterTableRemoveColumnClause($column); } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $alterClause = $this->getAlterTableChangeColumnClause($columnDiff); if ($alterClause !== null) { $alterClauses[] = $alterClause; } if (! $columnDiff->hasChanged('comment')) { continue; } $column = $columnDiff->column; $commentsSQL[] = $this->getCommentOnColumnSQL( $diff->getName($this)->getQuotedName($this), $column->getQuotedName($this), $this->getColumnComment($column) ); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . $this->getAlterTableRenameColumnClause($oldColumnName, $column); } if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (! empty($alterClauses)) { $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses); } $sql = array_merge($sql, $commentsSQL); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . $this->getAlterTableRenameTableClause($newName); } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); } return array_merge($sql, $tableSql, $columnSql); } /** * Returns the SQL clause for creating a column in a table alteration. * * @param Column $column The column to add. * * @return string */ protected function getAlterTableAddColumnClause(Column $column) { return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); } /** * Returns the SQL clause for altering a table. * * @param Identifier $tableName The quoted name of the table to alter. * * @return string */ protected function getAlterTableClause(Identifier $tableName) { return 'ALTER TABLE ' . $tableName->getQuotedName($this); } /** * Returns the SQL clause for dropping a column in a table alteration. * * @param Column $column The column to drop. * * @return string */ protected function getAlterTableRemoveColumnClause(Column $column) { return 'DROP ' . $column->getQuotedName($this); } /** * Returns the SQL clause for renaming a column in a table alteration. * * @param string $oldColumnName The quoted name of the column to rename. * @param Column $column The column to rename to. * * @return string */ protected function getAlterTableRenameColumnClause($oldColumnName, Column $column) { $oldColumnName = new Identifier($oldColumnName); return 'RENAME ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); } /** * Returns the SQL clause for renaming a table in a table alteration. * * @param Identifier $newTableName The quoted name of the table to rename to. * * @return string */ protected function getAlterTableRenameTableClause(Identifier $newTableName) { return 'RENAME ' . $newTableName->getQuotedName($this); } /** * Returns the SQL clause for altering a column in a table alteration. * * This method returns null in case that only the column comment has changed. * Changes in column comments have to be handled differently. * * @param ColumnDiff $columnDiff The diff of the column to alter. * * @return string|null */ protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff) { $column = $columnDiff->column; // Do not return alter clause if only comment has changed. if (! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) { $columnAlterationClause = 'ALTER ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); if ($columnDiff->hasChanged('default') && $column->getDefault() === null) { $columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT'; } return $columnAlterationClause; } return null; } /** * {@inheritdoc} */ public function getBigIntTypeDeclarationSQL(array $column) { $column['integer_type'] = 'BIGINT'; return $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getBinaryDefaultLength() { return 1; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 32767; } /** * {@inheritdoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'LONG BINARY'; } /** * {@inheritdoc} * * BIT type columns require an explicit NULL declaration * in SQL Anywhere if they shall be nullable. * Otherwise by just omitting the NOT NULL clause, * SQL Anywhere will declare them NOT NULL nonetheless. */ public function getBooleanTypeDeclarationSQL(array $column) { $nullClause = isset($column['notnull']) && (bool) $column['notnull'] === false ? ' NULL' : ''; return 'BIT' . $nullClause; } /** * {@inheritdoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'TEXT'; } /** * {@inheritdoc} */ public function getCommentOnColumnSQL($tableName, $columnName, $comment) { $tableName = new Identifier($tableName); $columnName = new Identifier($columnName); $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); return sprintf( 'COMMENT ON COLUMN %s.%s IS %s', $tableName->getQuotedName($this), $columnName->getQuotedName($this), $comment ); } /** * {@inheritdoc} */ public function getConcatExpression() { return 'STRING(' . implode(', ', func_get_args()) . ')'; } /** * {@inheritdoc} */ public function getCreateConstraintSQL(Constraint $constraint, $table) { if ($constraint instanceof ForeignKeyConstraint) { return $this->getCreateForeignKeySQL($constraint, $table); } if ($table instanceof Table) { $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this)); } /** * {@inheritdoc} */ public function getCreateDatabaseSQL($name) { $name = new Identifier($name); return "CREATE DATABASE '" . $name->getName() . "'"; } /** * {@inheritdoc} * * Appends SQL Anywhere specific flags if given. */ public function getCreateIndexSQL(Index $index, $table) { return parent::getCreateIndexSQL($index, $table) . $this->getAdvancedIndexOptionsSQL($index); } /** * {@inheritdoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { $table = $table->getQuotedName($this); } return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index); } /** * {@inheritdoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE'; } /** * {@inheritdoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritdoc} */ public function getCurrentDateSQL() { return 'CURRENT DATE'; } /** * {@inheritdoc} */ public function getCurrentTimeSQL() { return 'CURRENT TIME'; } /** * {@inheritdoc} */ public function getCurrentTimestampSQL() { return 'CURRENT TIMESTAMP'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $factorClause = ''; if ($operator === '-') { $factorClause = '-1 * '; } return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; } /** * {@inheritdoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')'; } /** * {@inheritdoc} */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.u'; } /** * {@inheritdoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritdoc} */ public function getDateTimeTzFormatString() { return $this->getDateTimeFormatString(); } /** * {@inheritdoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritdoc} */ public function getDefaultTransactionIsolationLevel() { return TransactionIsolationLevel::READ_UNCOMMITTED; } /** * {@inheritdoc} */ public function getDropDatabaseSQL($name) { $name = new Identifier($name); return "DROP DATABASE '" . $name->getName() . "'"; } /** * {@inheritdoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { $index = $index->getQuotedName($this); } if (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' ); } if (! isset($table)) { return 'DROP INDEX ' . $index; } if ($table instanceof Table) { $table = $table->getQuotedName($this); } if (! is_string($table)) { throw new InvalidArgumentException( __METHOD__ . '() expects $table parameter to be string or ' . Index::class . '.' ); } return 'DROP INDEX ' . $table . '.' . $index; } /** * {@inheritdoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritdoc} */ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = ''; $foreignKeyName = $foreignKey->getName(); $localColumns = $foreignKey->getQuotedLocalColumns($this); $foreignColumns = $foreignKey->getQuotedForeignColumns($this); $foreignTableName = $foreignKey->getQuotedForeignTableName($this); if (! empty($foreignKeyName)) { $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; } if (empty($localColumns)) { throw new InvalidArgumentException("Incomplete definition. 'local' required."); } if (empty($foreignColumns)) { throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); } if (empty($foreignTableName)) { throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); } if ($foreignKey->hasOption('notnull') && (bool) $foreignKey->getOption('notnull')) { $sql .= 'NOT NULL '; } return $sql . 'FOREIGN KEY (' . $this->getIndexFieldDeclarationListSQL($localColumns) . ') ' . 'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . ' (' . $this->getIndexFieldDeclarationListSQL($foreignColumns) . ')'; } /** * Returns foreign key MATCH clause for given type. * * @param int $type The foreign key match type * * @return string * * @throws InvalidArgumentException If unknown match type given. */ public function getForeignKeyMatchClauseSQL($type) { switch ((int) $type) { case self::FOREIGN_KEY_MATCH_SIMPLE: return 'SIMPLE'; case self::FOREIGN_KEY_MATCH_FULL: return 'FULL'; case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE: return 'UNIQUE SIMPLE'; case self::FOREIGN_KEY_MATCH_FULL_UNIQUE: return 'UNIQUE FULL'; default: throw new InvalidArgumentException('Invalid foreign key match type: ' . $type); } } /** * {@inheritdoc} */ public function getForeignKeyReferentialActionSQL($action) { // NO ACTION is not supported, therefore falling back to RESTRICT. if (strtoupper($action) === 'NO ACTION') { return 'RESTRICT'; } return parent::getForeignKeyReferentialActionSQL($action); } /** * {@inheritdoc} */ public function getForUpdateSQL() { return ''; } /** * {@inheritdoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'NEWID()'; } /** * {@inheritdoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UNIQUEIDENTIFIER'; } /** * {@inheritdoc} */ public function getIndexDeclarationSQL($name, Index $index) { // Index declaration in statements like CREATE TABLE is not supported. throw Exception::notSupported(__METHOD__); } /** * {@inheritdoc} */ public function getIntegerTypeDeclarationSQL(array $column) { $column['integer_type'] = 'INT'; return $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getListDatabasesSQL() { return 'SELECT db_name(number) AS name FROM sa_db_list()'; } /** * {@inheritdoc} */ public function getListTableColumnsSQL($table, $database = null) { $user = 'USER_NAME()'; if (strpos($table, '.') !== false) { [$user, $table] = explode('.', $table); $user = $this->quoteStringLiteral($user); } return sprintf( <<<'SQL' SELECT col.column_name, COALESCE(def.user_type_name, def.domain_name) AS 'type', def.declared_width AS 'length', def.scale, CHARINDEX('unsigned', def.domain_name) AS 'unsigned', IF col.nulls = 'Y' THEN 0 ELSE 1 ENDIF AS 'notnull', col."default", def.is_autoincrement AS 'autoincrement', rem.remarks AS 'comment' FROM sa_describe_query('SELECT * FROM "%s"') AS def JOIN SYS.SYSTABCOL AS col ON col.table_id = def.base_table_id AND col.column_id = def.base_column_id LEFT JOIN SYS.SYSREMARK AS rem ON col.object_id = rem.object_id WHERE def.base_owner_name = %s ORDER BY def.base_column_id ASC SQL , $table, $user ); } /** * {@inheritdoc} * * @todo Where is this used? Which information should be retrieved? */ public function getListTableConstraintsSQL($table) { $user = ''; if (strpos($table, '.') !== false) { [$user, $table] = explode('.', $table); $user = $this->quoteStringLiteral($user); $table = $this->quoteStringLiteral($table); } else { $table = $this->quoteStringLiteral($table); } return sprintf( <<<'SQL' SELECT con.* FROM SYS.SYSCONSTRAINT AS con JOIN SYS.SYSTAB AS tab ON con.table_object_id = tab.object_id WHERE tab.table_name = %s AND tab.creator = USER_ID(%s) SQL , $table, $user ); } /** * {@inheritdoc} */ public function getListTableForeignKeysSQL($table) { $user = ''; if (strpos($table, '.') !== false) { [$user, $table] = explode('.', $table); $user = $this->quoteStringLiteral($user); $table = $this->quoteStringLiteral($table); } else { $table = $this->quoteStringLiteral($table); } return sprintf( <<<'SQL' SELECT fcol.column_name AS local_column, ptbl.table_name AS foreign_table, pcol.column_name AS foreign_column, idx.index_name, IF fk.nulls = 'N' THEN 1 ELSE NULL ENDIF AS notnull, CASE ut.referential_action WHEN 'C' THEN 'CASCADE' WHEN 'D' THEN 'SET DEFAULT' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS on_update, CASE dt.referential_action WHEN 'C' THEN 'CASCADE' WHEN 'D' THEN 'SET DEFAULT' WHEN 'N' THEN 'SET NULL' WHEN 'R' THEN 'RESTRICT' ELSE NULL END AS on_delete, IF fk.check_on_commit = 'Y' THEN 1 ELSE NULL ENDIF AS check_on_commit, -- check_on_commit flag IF ftbl.clustered_index_id = idx.index_id THEN 1 ELSE NULL ENDIF AS 'clustered', -- clustered flag IF fk.match_type = 0 THEN NULL ELSE fk.match_type ENDIF AS 'match', -- match option IF pidx.max_key_distance = 1 THEN 1 ELSE NULL ENDIF AS for_olap_workload -- for_olap_workload flag FROM SYS.SYSFKEY AS fk JOIN SYS.SYSIDX AS idx ON fk.foreign_table_id = idx.table_id AND fk.foreign_index_id = idx.index_id JOIN SYS.SYSPHYSIDX pidx ON idx.table_id = pidx.table_id AND idx.phys_index_id = pidx.phys_index_id JOIN SYS.SYSTAB AS ptbl ON fk.primary_table_id = ptbl.table_id JOIN SYS.SYSTAB AS ftbl ON fk.foreign_table_id = ftbl.table_id JOIN SYS.SYSIDXCOL AS idxcol ON idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id JOIN SYS.SYSTABCOL AS pcol ON ptbl.table_id = pcol.table_id AND idxcol.primary_column_id = pcol.column_id JOIN SYS.SYSTABCOL AS fcol ON ftbl.table_id = fcol.table_id AND idxcol.column_id = fcol.column_id LEFT JOIN SYS.SYSTRIGGER ut ON fk.foreign_table_id = ut.foreign_table_id AND fk.foreign_index_id = ut.foreign_key_id AND ut.event = 'C' LEFT JOIN SYS.SYSTRIGGER dt ON fk.foreign_table_id = dt.foreign_table_id AND fk.foreign_index_id = dt.foreign_key_id AND dt.event = 'D' WHERE ftbl.table_name = %s AND ftbl.creator = USER_ID(%s) ORDER BY fk.foreign_index_id ASC, idxcol.sequence ASC SQL , $table, $user ); } /** * {@inheritdoc} */ public function getListTableIndexesSQL($table, $database = null) { $user = ''; if (strpos($table, '.') !== false) { [$user, $table] = explode('.', $table); $user = $this->quoteStringLiteral($user); $table = $this->quoteStringLiteral($table); } else { $table = $this->quoteStringLiteral($table); } return sprintf( <<<'SQL' SELECT idx.index_name AS key_name, IF idx.index_category = 1 THEN 1 ELSE 0 ENDIF AS 'primary', col.column_name, IF idx."unique" IN(1, 2, 5) THEN 0 ELSE 1 ENDIF AS non_unique, IF tbl.clustered_index_id = idx.index_id THEN 1 ELSE NULL ENDIF AS 'clustered', -- clustered flag IF idx."unique" = 5 THEN 1 ELSE NULL ENDIF AS with_nulls_not_distinct, -- with_nulls_not_distinct flag IF pidx.max_key_distance = 1 THEN 1 ELSE NULL ENDIF AS for_olap_workload -- for_olap_workload flag FROM SYS.SYSIDX AS idx JOIN SYS.SYSPHYSIDX pidx ON idx.table_id = pidx.table_id AND idx.phys_index_id = pidx.phys_index_id JOIN SYS.SYSIDXCOL AS idxcol ON idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id JOIN SYS.SYSTABCOL AS col ON idxcol.table_id = col.table_id AND idxcol.column_id = col.column_id JOIN SYS.SYSTAB AS tbl ON idx.table_id = tbl.table_id WHERE tbl.table_name = %s AND tbl.creator = USER_ID(%s) AND idx.index_category != 2 -- exclude indexes implicitly created by foreign key constraints ORDER BY idx.index_id ASC, idxcol.sequence ASC SQL , $table, $user ); } /** * {@inheritdoc} */ public function getListTablesSQL() { return "SELECT tbl.table_name FROM SYS.SYSTAB AS tbl JOIN SYS.SYSUSER AS usr ON tbl.creator = usr.user_id JOIN dbo.SYSOBJECTS AS obj ON tbl.object_id = obj.id WHERE tbl.table_type IN(1, 3) -- 'BASE', 'GBL TEMP' AND usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users AND obj.type = 'U' -- user created tables only ORDER BY tbl.table_name ASC"; } /** * {@inheritdoc} * * @todo Where is this used? Which information should be retrieved? */ public function getListUsersSQL() { return 'SELECT * FROM SYS.SYSUSER ORDER BY user_name ASC'; } /** * {@inheritdoc} */ public function getListViewsSQL($database) { return "SELECT tbl.table_name, v.view_def FROM SYS.SYSVIEW v JOIN SYS.SYSTAB tbl ON v.view_object_id = tbl.object_id JOIN SYS.SYSUSER usr ON tbl.creator = usr.user_id JOIN dbo.SYSOBJECTS obj ON tbl.object_id = obj.id WHERE usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users ORDER BY tbl.table_name ASC"; } /** * {@inheritdoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $str . ', ' . $substr . ')'; } return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')'; } /** * {@inheritdoc} */ public function getMaxIdentifierLength() { return 128; } /** * {@inheritdoc} */ public function getMd5Expression($column) { return 'HASH(' . $column . ", 'MD5')"; } /** * {@inheritdoc} */ public function getName() { return 'sqlanywhere'; } /** * Obtain DBMS specific SQL code portion needed to set a primary key * declaration to be used in statements like ALTER TABLE. * * @param Index $index Index definition * @param string $name Name of the primary key * * @return string DBMS specific SQL code portion needed to set a primary key * * @throws InvalidArgumentException If the given index is not a primary key. */ public function getPrimaryKeyDeclarationSQL(Index $index, $name = null) { if (! $index->isPrimary()) { throw new InvalidArgumentException( 'Can only create primary key declarations with getPrimaryKeyDeclarationSQL()' ); } return $this->getTableConstraintDeclarationSQL($index, $name); } /** * {@inheritdoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TEMPORARY OPTION isolation_level = ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritdoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { $column['integer_type'] = 'SMALLINT'; return $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * Returns the SQL statement for starting an existing database. * * In SQL Anywhere you can start and stop databases on a * database server instance. * This is a required statement after having created a new database * as it has to be explicitly started to be usable. * SQL Anywhere does not automatically start a database after creation! * * @param string $database Name of the database to start. * * @return string */ public function getStartDatabaseSQL($database) { $database = new Identifier($database); return "START DATABASE '" . $database->getName() . "' AUTOSTOP OFF"; } /** * Returns the SQL statement for stopping a running database. * * In SQL Anywhere you can start and stop databases on a * database server instance. * This is a required statement before dropping an existing database * as it has to be explicitly stopped before it can be dropped. * * @param string $database Name of the database to stop. * * @return string */ public function getStopDatabaseSQL($database) { $database = new Identifier($database); return 'STOP DATABASE "' . $database->getName() . '" UNCONDITIONALLY'; } /** * {@inheritdoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length === null) { return 'SUBSTRING(' . $string . ', ' . $start . ')'; } return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')'; } /** * {@inheritdoc} */ public function getTemporaryTableSQL() { return 'GLOBAL TEMPORARY'; } /** * {@inheritdoc} */ public function getTimeFormatString() { return 'H:i:s.u'; } /** * {@inheritdoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritdoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { if (! $char) { switch ($mode) { case TrimMode::LEADING: return $this->getLtrimExpression($str); case TrimMode::TRAILING: return $this->getRtrimExpression($str); default: return 'TRIM(' . $str . ')'; } } $pattern = "'%[^' + " . $char . " + ']%'"; switch ($mode) { case TrimMode::LEADING: return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))'; case TrimMode::TRAILING: return 'REVERSE(SUBSTR(REVERSE(' . $str . '), PATINDEX(' . $pattern . ', REVERSE(' . $str . '))))'; default: return 'REVERSE(SUBSTR(REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))), ' . 'PATINDEX(' . $pattern . ', ' . 'REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))))))'; } } /** * {@inheritdoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritdoc} */ public function getUniqueConstraintDeclarationSQL($name, Index $index) { if ($index->isPrimary()) { throw new InvalidArgumentException( 'Cannot create primary key constraint declarations with getUniqueConstraintDeclarationSQL().' ); } if (! $index->isUnique()) { throw new InvalidArgumentException( 'Can only create unique constraint declarations, no common index declarations with ' . 'getUniqueConstraintDeclarationSQL().' ); } return $this->getTableConstraintDeclarationSQL($index, $name); } /** * {@inheritdoc} */ public function getVarcharDefaultLength() { return 1; } /** * {@inheritdoc} */ public function getVarcharMaxLength() { return 32767; } /** * {@inheritdoc} */ public function hasNativeGuidType() { return true; } /** * {@inheritdoc} */ public function prefersIdentityColumns() { return true; } /** * {@inheritdoc} */ public function supportsCommentOnStatement() { return true; } /** * {@inheritdoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritdoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { $unsigned = ! empty($column['unsigned']) ? 'UNSIGNED ' : ''; $autoincrement = ! empty($column['autoincrement']) ? ' IDENTITY' : ''; return $unsigned . $column['integer_type'] . $autoincrement; } /** * {@inheritdoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $columnListSql = $this->getColumnDeclarationListSQL($columns); $indexSql = []; if (! empty($options['uniqueConstraints'])) { foreach ((array) $options['uniqueConstraints'] as $name => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); } } if (! empty($options['indexes'])) { foreach ((array) $options['indexes'] as $index) { assert($index instanceof Index); $indexSql[] = $this->getCreateIndexSQL($index, $name); } } if (! empty($options['primary'])) { $flags = ''; if (isset($options['primary_index']) && $options['primary_index']->hasFlag('clustered')) { $flags = ' CLUSTERED '; } $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values((array) $options['primary']))) . ')'; } if (! empty($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $columnListSql .= ', ' . $this->getForeignKeyDeclarationSQL($definition); } } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; return array_merge([$query], $indexSql); } /** * {@inheritdoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return '0'; case TransactionIsolationLevel::READ_COMMITTED: return '1'; case TransactionIsolationLevel::REPEATABLE_READ: return '2'; case TransactionIsolationLevel::SERIALIZABLE: return '3'; default: throw new InvalidArgumentException('Invalid isolation level:' . $level); } } /** * {@inheritdoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { $limitOffsetClause = $this->getTopClauseSQL($limit, $offset); if ($limitOffsetClause === '') { return $query; } if (! preg_match('/^\s*(SELECT\s+(DISTINCT\s+)?)(.*)/i', $query, $matches)) { return $query; } return $matches[1] . $limitOffsetClause . ' ' . $matches[3]; } private function getTopClauseSQL(?int $limit, ?int $offset): string { if ($offset > 0) { return sprintf('TOP %s START AT %d', $limit ?? 'ALL', $offset + 1); } return $limit === null ? '' : 'TOP ' . $limit; } /** * Return the INDEX query section dealing with non-standard * SQL Anywhere options. * * @param Index $index Index definition * * @return string */ protected function getAdvancedIndexOptionsSQL(Index $index) { $sql = ''; if (! $index->isPrimary() && $index->hasFlag('for_olap_workload')) { $sql .= ' FOR OLAP WORKLOAD'; } return $sql; } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? 'BINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')' : 'VARBINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')'; } /** * Returns the SQL snippet for creating a table constraint. * * @param Constraint $constraint The table constraint to create the SQL snippet for. * @param string|null $name The table constraint name to use if any. * * @return string * * @throws InvalidArgumentException If the given table constraint type is not supported by this method. */ protected function getTableConstraintDeclarationSQL(Constraint $constraint, $name = null) { if ($constraint instanceof ForeignKeyConstraint) { return $this->getForeignKeyDeclarationSQL($constraint); } if (! $constraint instanceof Index) { throw new InvalidArgumentException('Unsupported constraint type: ' . get_class($constraint)); } if (! $constraint->isPrimary() && ! $constraint->isUnique()) { throw new InvalidArgumentException( 'Can only create primary, unique or foreign key constraint declarations, no common index declarations' . ' with getTableConstraintDeclarationSQL().' ); } $constraintColumns = $constraint->getQuotedColumns($this); if (empty($constraintColumns)) { throw new InvalidArgumentException("Incomplete definition. 'columns' required."); } $sql = ''; $flags = ''; if (! empty($name)) { $name = new Identifier($name); $sql .= 'CONSTRAINT ' . $name->getQuotedName($this) . ' '; } if ($constraint->hasFlag('clustered')) { $flags = 'CLUSTERED '; } if ($constraint->isPrimary()) { return $sql . 'PRIMARY KEY ' . $flags . '(' . $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; } return $sql . 'UNIQUE ' . $flags . '(' . $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; } /** * {@inheritdoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->hasFlag('virtual')) { $type .= 'VIRTUAL '; } if ($index->isUnique()) { $type .= 'UNIQUE '; } if ($index->hasFlag('clustered')) { $type .= 'CLUSTERED '; } return $type; } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return ['ALTER INDEX ' . $oldIndexName . ' ON ' . $tableName . ' RENAME TO ' . $index->getQuotedName($this)]; } /** * {@inheritdoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLAnywhereKeywords::class; } /** * {@inheritdoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(' . $this->getVarcharDefaultLength() . ')') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(' . $this->getVarcharDefaultLength() . ')'); } /** * {@inheritdoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'char' => 'string', 'long nvarchar' => 'text', 'long varchar' => 'text', 'nchar' => 'string', 'ntext' => 'text', 'nvarchar' => 'string', 'text' => 'text', 'uniqueidentifierstr' => 'guid', 'varchar' => 'string', 'xml' => 'text', 'bigint' => 'bigint', 'unsigned bigint' => 'bigint', 'bit' => 'boolean', 'decimal' => 'decimal', 'double' => 'float', 'float' => 'float', 'int' => 'integer', 'integer' => 'integer', 'unsigned int' => 'integer', 'numeric' => 'decimal', 'smallint' => 'smallint', 'unsigned smallint' => 'smallint', 'tinyint' => 'smallint', 'unsigned tinyint' => 'smallint', 'money' => 'decimal', 'smallmoney' => 'decimal', 'long varbit' => 'text', 'varbit' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'smalldatetime' => 'datetime', 'time' => 'time', 'timestamp' => 'datetime', 'binary' => 'binary', 'image' => 'blob', 'long binary' => 'blob', 'uniqueidentifier' => 'guid', 'varbinary' => 'binary', ]; } } dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php 0000644 00000001657 15120025741 0016326 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Table; /** * Platform to ensure compatibility of Doctrine with SQL Azure * * On top of SQL Server 2008 the following functionality is added: * * - Create tables with the FEDERATED ON syntax. * * @deprecated */ class SQLAzurePlatform extends SQLServer2008Platform { /** * {@inheritDoc} */ public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) { $sql = parent::getCreateTableSQL($table, $createFlags); if ($table->hasOption('azure.federatedOnColumnName')) { $distributionName = $table->getOption('azure.federatedOnDistributionName'); $columnName = $table->getOption('azure.federatedOnColumnName'); $stmt = ' FEDERATED ON (' . $distributionName . ' = ' . $columnName . ')'; $sql[0] .= $stmt; } return $sql; } } dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php 0000644 00000002273 15120025741 0017010 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2005 version and * higher. * * Differences to SQL Server 2008 are: * * - DATETIME2 datatype does not exist, only DATETIME which has a precision of * 3. This is not supported by PHP DateTime, so we are emulating it by * setting .000 manually. * - Starting with SQLServer2005 VARCHAR(MAX), VARBINARY(MAX) and * NVARCHAR(max) replace the old TEXT, NTEXT and IMAGE types. See * {@link http://www.sql-server-helper.com/faq/sql-server-2005-varchar-max-p01.aspx} * for more information. * * @deprecated Use SQL Server 2012 or newer */ class SQLServer2005Platform extends SQLServerPlatform { /** * {@inheritDoc} */ public function supportsLimitOffset() { return true; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'VARCHAR(MAX)'; } /** * {@inheritdoc} * * Returns Microsoft SQL Server 2005 specific keywords class */ protected function getReservedKeywordsClass() { return Keywords\SQLServer2005Keywords::class; } } dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php 0000644 00000005411 15120025741 0017010 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; /** * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2008 version. * * Differences to SQL Server 2005 and before are that a new DATETIME2 type was * introduced that has a higher precision. * * @deprecated Use SQL Server 2012 or newer */ class SQLServer2008Platform extends SQLServer2005Platform { /** * {@inheritDoc} */ public function getListTablesSQL() { // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects' . " WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { // 3 - microseconds precision length // http://msdn.microsoft.com/en-us/library/ms187819.aspx return 'DATETIME2(6)'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME(0)'; } /** * {@inheritDoc} */ public function getDateTimeTzTypeDeclarationSQL(array $column) { return 'DATETIMEOFFSET(6)'; } /** * {@inheritDoc} */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.u'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s.u P'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return 'H:i:s'; } /** * {@inheritDoc} * * Adding Datetime2 Type */ protected function initializeDoctrineTypeMappings() { parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['datetime2'] = 'datetime'; $this->doctrineTypeMapping['date'] = 'date'; $this->doctrineTypeMapping['time'] = 'time'; $this->doctrineTypeMapping['datetimeoffset'] = 'datetimetz'; } /** * {@inheritdoc} * * Returns Microsoft SQL Server 2008 specific keywords class */ protected function getReservedKeywordsClass() { return Keywords\SQLServer2008Keywords::class; } protected function getLikeWildcardCharacters(): string { return parent::getLikeWildcardCharacters() . '[]^'; } } dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php 0000644 00000012316 15120025741 0017005 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\Sequence; use function preg_match; use function preg_match_all; use function substr_count; use const PREG_OFFSET_CAPTURE; /** * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2012 version. * * Differences to SQL Server 2008 and before are that sequences are introduced, * and support for the new OFFSET... FETCH syntax for result pagination has been added. */ class SQLServer2012Platform extends SQLServer2008Platform { /** * {@inheritdoc} */ public function getAlterSequenceSQL(Sequence $sequence) { return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . ' INCREMENT BY ' . $sequence->getAllocationSize(); } /** * {@inheritdoc} */ public function getCreateSequenceSQL(Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . ' START WITH ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue(); } /** * {@inheritdoc} */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof Sequence) { $sequence = $sequence->getQuotedName($this); } return 'DROP SEQUENCE ' . $sequence; } /** * {@inheritdoc} */ public function getListSequencesSQL($database) { return 'SELECT seq.name, CAST( seq.increment AS VARCHAR(MAX) ) AS increment, -- CAST avoids driver error for sql_variant type CAST( seq.start_value AS VARCHAR(MAX) ) AS start_value -- CAST avoids driver error for sql_variant type FROM sys.sequences AS seq'; } /** * {@inheritdoc} */ public function getSequenceNextValSQL($sequence) { return 'SELECT NEXT VALUE FOR ' . $sequence; } /** * {@inheritdoc} */ public function supportsSequences() { return true; } /** * {@inheritdoc} * * Returns Microsoft SQL Server 2012 specific keywords class */ protected function getReservedKeywordsClass() { return Keywords\SQLServer2012Keywords::class; } /** * {@inheritdoc} */ protected function doModifyLimitQuery($query, $limit, $offset = null) { if ($limit === null && $offset <= 0) { return $query; } // Queries using OFFSET... FETCH MUST have an ORDER BY clause if ($this->shouldAddOrderBy($query)) { if (preg_match('/^SELECT\s+DISTINCT/im', $query)) { // SQL Server won't let us order by a non-selected column in a DISTINCT query, // so we have to do this madness. This says, order by the first column in the // result. SQL Server's docs say that a nonordered query's result order is non- // deterministic anyway, so this won't do anything that a bunch of update and // deletes to the table wouldn't do anyway. $query .= ' ORDER BY 1'; } else { // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you // use constant expressions in the order by list. $query .= ' ORDER BY (SELECT 0)'; } } if ($offset === null) { $offset = 0; } // This looks somewhat like MYSQL, but limit/offset are in inverse positions // Supposedly SQL:2008 core standard. // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. $query .= ' OFFSET ' . (int) $offset . ' ROWS'; if ($limit !== null) { $query .= ' FETCH NEXT ' . (int) $limit . ' ROWS ONLY'; } return $query; } /** * @param string $query */ private function shouldAddOrderBy($query): bool { // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement // but can be in a newline $matches = []; $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE); if ($matchesCount === 0) { return true; } // ORDER BY instance may be in a subquery after ORDER BY // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2) // if in the searched query ORDER BY clause was found where // number of open parentheses after the occurrence of the clause is equal to // number of closed brackets after the occurrence of the clause, // it means that ORDER BY is included in the query being checked while ($matchesCount > 0) { $orderByPos = $matches[0][--$matchesCount][1]; $openBracketsCount = substr_count($query, '(', $orderByPos); $closedBracketsCount = substr_count($query, ')', $orderByPos); if ($openBracketsCount === $closedBracketsCount) { return false; } } return true; } } dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php 0000644 00000151211 15120025741 0016476 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ColumnDiff; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use InvalidArgumentException; use function array_merge; use function array_unique; use function array_values; use function count; use function crc32; use function dechex; use function explode; use function func_get_args; use function implode; use function is_array; use function is_bool; use function is_numeric; use function is_string; use function preg_match; use function sprintf; use function str_replace; use function stripos; use function stristr; use function strlen; use function strpos; use function strtoupper; use function substr; use function substr_count; /** * The SQLServerPlatform provides the behavior, features and SQL dialect of the * Microsoft SQL Server database platform. * * @deprecated Use SQL Server 2012 or newer */ class SQLServerPlatform extends AbstractPlatform { /** * {@inheritdoc} */ public function getCurrentDateSQL() { return $this->getConvertExpression('date', 'GETDATE()'); } /** * {@inheritdoc} */ public function getCurrentTimeSQL() { return $this->getConvertExpression('time', 'GETDATE()'); } /** * Returns an expression that converts an expression of one data type to another. * * @param string $dataType The target native data type. Alias data types cannot be used. * @param string $expression The SQL expression to convert. * * @return string */ private function getConvertExpression($dataType, $expression) { return sprintf('CONVERT(%s, %s)', $dataType, $expression); } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { $factorClause = ''; if ($operator === '-') { $factorClause = '-1 * '; } return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')'; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; } /** * {@inheritDoc} * * Microsoft SQL Server prefers "autoincrement" identity columns * since sequences can only be emulated with a table. */ public function prefersIdentityColumns() { return true; } /** * {@inheritDoc} * * Microsoft SQL Server supports this through AUTO_INCREMENT columns. */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsReleaseSavepoints() { return false; } /** * {@inheritdoc} */ public function supportsSchemas() { return true; } /** * {@inheritdoc} */ public function getDefaultSchemaName() { return 'dbo'; } /** * {@inheritDoc} */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function hasNativeGuidType() { return true; } /** * {@inheritDoc} */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * {@inheritDoc} */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * {@inheritDoc} */ public function supportsCreateDropDatabase() { return true; } /** * {@inheritDoc} */ public function getCreateSchemaSQL($schemaName) { return 'CREATE SCHEMA ' . $schemaName; } /** * {@inheritDoc} */ public function getDropForeignKeySQL($foreignKey, $table) { if (! $foreignKey instanceof ForeignKeyConstraint) { $foreignKey = new Identifier($foreignKey); } if (! $table instanceof Table) { $table = new Identifier($table); } $foreignKey = $foreignKey->getQuotedName($this); $table = $table->getQuotedName($this); return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * {@inheritDoc} */ public function getDropIndexSQL($index, $table = null) { if ($index instanceof Index) { $index = $index->getQuotedName($this); } elseif (! is_string($index)) { throw new InvalidArgumentException( __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' ); } if (! isset($table)) { return 'DROP INDEX ' . $index; } if ($table instanceof Table) { $table = $table->getQuotedName($this); } return sprintf( " IF EXISTS (SELECT * FROM sysobjects WHERE name = '%s') ALTER TABLE %s DROP CONSTRAINT %s ELSE DROP INDEX %s ON %s ", $index, $table, $index, $index, $table ); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $defaultConstraintsSql = []; $commentsSql = []; $tableComment = $options['comment'] ?? null; if ($tableComment !== null) { $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment); } // @todo does other code breaks because of this? // force primary keys to be not null foreach ($columns as &$column) { if (isset($column['primary']) && $column['primary']) { $column['notnull'] = true; } // Build default constraints SQL statements. if (isset($column['default'])) { $defaultConstraintsSql[] = 'ALTER TABLE ' . $name . ' ADD' . $this->getDefaultConstraintDeclarationSQL($name, $column); } if (empty($column['comment']) && ! is_numeric($column['comment'])) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']); } $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $name => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $flags = ''; if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { $flags = ' NONCLUSTERED'; } $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql = [$query]; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } } if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $name); } } return array_merge($sql, $commentsSql, $defaultConstraintsSql); } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { if ($table instanceof Table) { $identifier = $table->getQuotedName($this); } else { $identifier = $table; } $sql = 'ALTER TABLE ' . $identifier . ' ADD PRIMARY KEY'; if ($index->hasFlag('nonclustered')) { $sql .= ' NONCLUSTERED'; } return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; } /** * Returns the SQL statement for creating a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to create the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getCreateColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaSQL, $tableSQL] = explode('.', $tableName); $schemaSQL = $this->quoteStringLiteral($schemaSQL); $tableSQL = $this->quoteStringLiteral($tableSQL); } else { $schemaSQL = "'dbo'"; $tableSQL = $this->quoteStringLiteral($tableName); } return $this->getAddExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $schemaSQL, 'TABLE', $tableSQL, 'COLUMN', $columnName ); } /** * Returns the SQL snippet for declaring a default constraint. * * @param string $table Name of the table to return the default constraint declaration for. * @param mixed[] $column Column definition. * * @return string * * @throws InvalidArgumentException */ public function getDefaultConstraintDeclarationSQL($table, array $column) { if (! isset($column['default'])) { throw new InvalidArgumentException("Incomplete column definition. 'default' required."); } $columnName = new Identifier($column['name']); return ' CONSTRAINT ' . $this->generateDefaultConstraintName($table, $column['name']) . $this->getDefaultValueDeclarationSQL($column) . ' FOR ' . $columnName->getQuotedName($this); } /** * {@inheritDoc} */ public function getUniqueConstraintDeclarationSQL($name, Index $index) { $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index); $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); return $constraint; } /** * {@inheritDoc} */ public function getCreateIndexSQL(Index $index, $table) { $constraint = parent::getCreateIndexSQL($index, $table); if ($index->isUnique() && ! $index->isPrimary()) { $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); } return $constraint; } /** * {@inheritDoc} */ protected function getCreateIndexSQLFlags(Index $index) { $type = ''; if ($index->isUnique()) { $type .= 'UNIQUE '; } if ($index->hasFlag('clustered')) { $type .= 'CLUSTERED '; } elseif ($index->hasFlag('nonclustered')) { $type .= 'NONCLUSTERED '; } return $type; } /** * Extend unique key constraint with required filters * * @param string $sql * * @return string */ private function _appendUniqueConstraintDefinition($sql, Index $index) { $fields = []; foreach ($index->getQuotedColumns($this) as $field) { $fields[] = $field . ' IS NOT NULL'; } return $sql . ' WHERE ' . implode(' AND ', $fields); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $queryParts = []; $sql = []; $columnSql = []; $commentsSql = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columnDef = $column->toArray(); $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); if (isset($columnDef['default'])) { $addColumnSql .= ' CONSTRAINT ' . $this->generateDefaultConstraintName($diff->name, $column->getQuotedName($this)) . $this->getDefaultValueDeclarationSQL($columnDef); } $queryParts[] = $addColumnSql; $comment = $this->getColumnComment($column); if (empty($comment) && ! is_numeric($comment)) { continue; } $commentsSql[] = $this->getCreateColumnCommentSQL( $diff->name, $column->getQuotedName($this), $comment ); } foreach ($diff->removedColumns as $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); } foreach ($diff->changedColumns as $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } $column = $columnDiff->column; $comment = $this->getColumnComment($column); $hasComment = ! empty($comment) || is_numeric($comment); if ($columnDiff->fromColumn instanceof Column) { $fromComment = $this->getColumnComment($columnDiff->fromColumn); $hasFromComment = ! empty($fromComment) || is_numeric($fromComment); if ($hasFromComment && $hasComment && $fromComment !== $comment) { $commentsSql[] = $this->getAlterColumnCommentSQL( $diff->name, $column->getQuotedName($this), $comment ); } elseif ($hasFromComment && ! $hasComment) { $commentsSql[] = $this->getDropColumnCommentSQL($diff->name, $column->getQuotedName($this)); } elseif (! $hasFromComment && $hasComment) { $commentsSql[] = $this->getCreateColumnCommentSQL( $diff->name, $column->getQuotedName($this), $comment ); } } // Do not add query part if only comment has changed. if ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1) { continue; } $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); if ($requireDropDefaultConstraint) { $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( $diff->name, $columnDiff->oldColumnName ); } $columnDef = $column->toArray(); $queryParts[] = 'ALTER COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); if ( ! isset($columnDef['default']) || (! $requireDropDefaultConstraint && ! $columnDiff->hasChanged('default')) ) { continue; } $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = new Identifier($oldColumnName); $sql[] = "sp_rename '" . $diff->getName($this)->getQuotedName($this) . '.' . $oldColumnName->getQuotedName($this) . "', '" . $column->getQuotedName($this) . "', 'COLUMN'"; // Recreate default constraint with new column name if necessary (for future reference). if ($column->getDefault() === null) { continue; } $queryParts[] = $this->getAlterTableDropDefaultConstraintClause( $diff->name, $oldColumnName->getQuotedName($this) ); $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column); } $tableSql = []; if ($this->onSchemaAlterTable($diff, $tableSql)) { return array_merge($tableSql, $columnSql); } foreach ($queryParts as $query) { $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; } $sql = array_merge($sql, $commentsSql); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = "sp_rename '" . $diff->getName($this)->getQuotedName($this) . "', '" . $newName->getName() . "'"; /** * Rename table's default constraints names * to match the new table name. * This is necessary to ensure that the default * constraints can be referenced in future table * alterations as the table name is encoded in * default constraints' names. */ $sql[] = "DECLARE @sql NVARCHAR(MAX) = N''; " . "SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' " . "+ REPLACE(dc.name, '" . $this->generateIdentifierName($diff->name) . "', " . "'" . $this->generateIdentifierName($newName->getName()) . "') + ''', ''OBJECT'';' " . 'FROM sys.default_constraints dc ' . 'JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id ' . "WHERE tbl.name = '" . $newName->getName() . "';" . 'EXEC sp_executesql @sql'; } $sql = array_merge( $this->getPreAlterTableIndexForeignKeySQL($diff), $sql, $this->getPostAlterTableIndexForeignKeySQL($diff) ); return array_merge($sql, $tableSql, $columnSql); } /** * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param Column $column The column to generate the clause for. * * @return string */ private function getAlterTableAddDefaultConstraintClause($tableName, Column $column) { $columnDef = $column->toArray(); $columnDef['name'] = $column->getQuotedName($this); return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef); } /** * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. * * @param string $tableName The name of the table to generate the clause for. * @param string $columnName The name of the column to generate the clause for. * * @return string */ private function getAlterTableDropDefaultConstraintClause($tableName, $columnName) { return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName); } /** * Checks whether a column alteration requires dropping its default constraint first. * * Different to other database vendors SQL Server implements column default values * as constraints and therefore changes in a column's default value as well as changes * in a column's type require dropping the default constraint first before being to * alter the particular column to the new definition. * * @param ColumnDiff $columnDiff The column diff to evaluate. * * @return bool True if the column alteration requires dropping its default constraint first, false otherwise. */ private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff) { // We can only decide whether to drop an existing default constraint // if we know the original default value. if (! $columnDiff->fromColumn instanceof Column) { return false; } // We only need to drop an existing default constraint if we know the // column was defined with a default value before. if ($columnDiff->fromColumn->getDefault() === null) { return false; } // We need to drop an existing default constraint if the column was // defined with a default value before and it has changed. if ($columnDiff->hasChanged('default')) { return true; } // We need to drop an existing default constraint if the column was // defined with a default value before and the native column type has changed. return $columnDiff->hasChanged('type') || $columnDiff->hasChanged('fixed'); } /** * Returns the SQL statement for altering a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to alter the comment for. * @param string|null $comment The column's comment. * * @return string */ protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) { if (strpos($tableName, '.') !== false) { [$schemaSQL, $tableSQL] = explode('.', $tableName); $schemaSQL = $this->quoteStringLiteral($schemaSQL); $tableSQL = $this->quoteStringLiteral($tableSQL); } else { $schemaSQL = "'dbo'"; $tableSQL = $this->quoteStringLiteral($tableName); } return $this->getUpdateExtendedPropertySQL( 'MS_Description', $comment, 'SCHEMA', $schemaSQL, 'TABLE', $tableSQL, 'COLUMN', $columnName ); } /** * Returns the SQL statement for dropping a column comment. * * SQL Server does not support native column comments, * therefore the extended properties functionality is used * as a workaround to store them. * The property name used to store column comments is "MS_Description" * which provides compatibility with SQL Server Management Studio, * as column comments are stored in the same property there when * specifying a column's "Description" attribute. * * @param string $tableName The quoted table name to which the column belongs. * @param string $columnName The quoted column name to drop the comment for. * * @return string */ protected function getDropColumnCommentSQL($tableName, $columnName) { if (strpos($tableName, '.') !== false) { [$schemaSQL, $tableSQL] = explode('.', $tableName); $schemaSQL = $this->quoteStringLiteral($schemaSQL); $tableSQL = $this->quoteStringLiteral($tableSQL); } else { $schemaSQL = "'dbo'"; $tableSQL = $this->quoteStringLiteral($tableName); } return $this->getDropExtendedPropertySQL( 'MS_Description', 'SCHEMA', $schemaSQL, 'TABLE', $tableSQL, 'COLUMN', $columnName ); } /** * {@inheritdoc} */ protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) { return [sprintf( "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'", $tableName, $oldIndexName, $index->getQuotedName($this) ), ]; } /** * Returns the SQL statement for adding an extended property to a database object. * * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx * * @param string $name The name of the property to add. * @param string|null $value The value of the property to add. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getAddExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_addextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' . 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; } /** * Returns the SQL statement for dropping an extended property from a database object. * * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx * * @param string $name The name of the property to drop. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getDropExtendedPropertySQL( $name, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_dropextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', ' . 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; } /** * Returns the SQL statement for updating an extended property of a database object. * * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx * * @param string $name The name of the property to update. * @param string|null $value The value of the property to update. * @param string|null $level0Type The type of the object at level 0 the property belongs to. * @param string|null $level0Name The name of the object at level 0 the property belongs to. * @param string|null $level1Type The type of the object at level 1 the property belongs to. * @param string|null $level1Name The name of the object at level 1 the property belongs to. * @param string|null $level2Type The type of the object at level 2 the property belongs to. * @param string|null $level2Name The name of the object at level 2 the property belongs to. * * @return string */ public function getUpdateExtendedPropertySQL( $name, $value = null, $level0Type = null, $level0Name = null, $level1Type = null, $level1Name = null, $level2Type = null, $level2Name = null ) { return 'EXEC sp_updateextendedproperty ' . 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' . 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' . 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name; } /** * {@inheritDoc} */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; } /** * {@inheritDoc} */ public function getListTablesSQL() { // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name"; } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { return "SELECT col.name, type.name AS type, col.max_length AS length, ~col.is_nullable AS notnull, def.definition AS [default], col.scale, col.precision, col.is_identity AS autoincrement, col.collation_name AS collation, CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type FROM sys.columns AS col JOIN sys.types AS type ON col.user_type_id = type.user_type_id JOIN sys.objects AS obj ON col.object_id = obj.object_id JOIN sys.schemas AS scm ON obj.schema_id = scm.schema_id LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND col.object_id = def.parent_object_id LEFT JOIN sys.extended_properties AS prop ON obj.object_id = prop.major_id AND col.column_id = prop.minor_id AND prop.name = 'MS_Description' WHERE obj.type = 'U' AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name'); } /** * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { return 'SELECT f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id WHERE ' . $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)'); } /** * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { return "SELECT idx.name AS key_name, col.name AS column_name, ~idx.is_unique AS non_unique, idx.is_primary_key AS [primary], CASE idx.type WHEN '1' THEN 'clustered' WHEN '2' THEN 'nonclustered' ELSE NULL END AS flags FROM sys.tables AS tbl JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . ' ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC'; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name"; } /** * Returns the where clause to filter schema and table name in a query. * * @param string $table The full qualified name of the table. * @param string $schemaColumn The name of the column to compare the schema to in the where clause. * @param string $tableColumn The name of the column to compare the table to in the where clause. * * @return string */ private function getTableWhereClause($table, $schemaColumn, $tableColumn) { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); $schema = $this->quoteStringLiteral($schema); $table = $this->quoteStringLiteral($table); } else { $schema = 'SCHEMA_NAME()'; $table = $this->quoteStringLiteral($table); } return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return 'NEWID()'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'CHARINDEX(' . $substr . ', ' . $str . ')'; } return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } /** * {@inheritDoc} */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { if (! $char) { switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: return 'LTRIM(RTRIM(' . $str . '))'; } return $trimFn . '(' . $str . ')'; } $pattern = "'%[^' + " . $char . " + ']%'"; if ($mode === TrimMode::LEADING) { return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; } if ($mode === TrimMode::TRAILING) { return 'reverse(stuff(reverse(' . $str . '), 1, ' . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; } return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, ' . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))'; } /** * {@inheritDoc} */ public function getConcatExpression() { $args = func_get_args(); return '(' . implode(' + ', $args) . ')'; } /** * {@inheritDoc} */ public function getListDatabasesSQL() { return 'SELECT * FROM sys.databases'; } /** * {@inheritDoc} */ public function getListNamespacesSQL() { return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')"; } /** * {@inheritDoc} */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTRING(' . $string . ', ' . $start . ', LEN(' . $string . ') - ' . $start . ' + 1)'; } /** * {@inheritDoc} */ public function getLengthExpression($column) { return 'LEN(' . $column . ')'; } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getGuidTypeDeclarationSQL(array $column) { return 'UNIQUEIDENTIFIER'; } /** * {@inheritDoc} */ public function getAsciiStringTypeDeclarationSQL(array $column): string { $length = $column['length'] ?? null; if (! isset($column['fixed'])) { return sprintf('VARCHAR(%d)', $length ?? 255); } return sprintf('CHAR(%d)', $length ?? 255); } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')'; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 8000; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'VARCHAR(MAX)'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { return ! empty($column['autoincrement']) ? ' IDENTITY' : ''; } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BIT'; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset = null) { $where = []; if ($offset > 0) { $where[] = sprintf('doctrine_rownum >= %d', $offset + 1); } if ($limit !== null) { $where[] = sprintf('doctrine_rownum <= %d', $offset + $limit); $top = sprintf('TOP %d', $offset + $limit); } else { $top = 'TOP 9223372036854775807'; } if (empty($where)) { return $query; } // We'll find a SELECT or SELECT distinct and prepend TOP n to it // Even if the TOP n is very large, the use of a CTE will // allow the SQL Server query planner to optimize it so it doesn't // actually scan the entire range covered by the TOP clause. if (! preg_match('/^(\s*SELECT\s+(?:DISTINCT\s+)?)(.*)$/is', $query, $matches)) { return $query; } $query = $matches[1] . $top . ' ' . $matches[2]; if (stristr($query, 'ORDER BY')) { // Inner order by is not valid in SQL Server for our purposes // unless it's in a TOP N subquery. $query = $this->scrubInnerOrderBy($query); } // Build a new limited query around the original, using a CTE return sprintf( 'WITH dctrn_cte AS (%s) ' . 'SELECT * FROM (' . 'SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM dctrn_cte' . ') AS doctrine_tbl ' . 'WHERE %s ORDER BY doctrine_rownum ASC', $query, implode(' AND ', $where) ); } /** * Remove ORDER BY clauses in subqueries - they're not supported by SQL Server. * Caveat: will leave ORDER BY in TOP N subqueries. * * @param string $query * * @return string */ private function scrubInnerOrderBy($query) { $count = substr_count(strtoupper($query), 'ORDER BY'); $offset = 0; while ($count-- > 0) { $orderByPos = stripos($query, ' ORDER BY', $offset); if ($orderByPos === false) { break; } $qLen = strlen($query); $parenCount = 0; $currentPosition = $orderByPos; while ($parenCount >= 0 && $currentPosition < $qLen) { if ($query[$currentPosition] === '(') { $parenCount++; } elseif ($query[$currentPosition] === ')') { $parenCount--; } $currentPosition++; } if ($this->isOrderByInTopNSubquery($query, $orderByPos)) { // If the order by clause is in a TOP N subquery, do not remove // it and continue iteration from the current position. $offset = $currentPosition; continue; } if ($currentPosition >= $qLen - 1) { continue; } $query = substr($query, 0, $orderByPos) . substr($query, $currentPosition - 1); $offset = $orderByPos; } return $query; } /** * Check an ORDER BY clause to see if it is in a TOP N query or subquery. * * @param string $query The query * @param int $currentPosition Start position of ORDER BY clause * * @return bool true if ORDER BY is in a TOP N query, false otherwise */ private function isOrderByInTopNSubquery($query, $currentPosition) { // Grab query text on the same nesting level as the ORDER BY clause we're examining. $subQueryBuffer = ''; $parenCount = 0; // If $parenCount goes negative, we've exited the subquery we're examining. // If $currentPosition goes negative, we've reached the beginning of the query. while ($parenCount >= 0 && $currentPosition >= 0) { if ($query[$currentPosition] === '(') { $parenCount--; } elseif ($query[$currentPosition] === ')') { $parenCount++; } // Only yank query text on the same nesting level as the ORDER BY clause. $subQueryBuffer = ($parenCount === 0 ? $query[$currentPosition] : ' ') . $subQueryBuffer; $currentPosition--; } return (bool) preg_match('/SELECT\s+(DISTINCT\s+)?TOP\s/i', $subQueryBuffer); } /** * {@inheritDoc} */ public function supportsLimitOffset() { return false; } /** * {@inheritDoc} */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (! is_bool($value) && ! is_numeric($value)) { continue; } $item[$key] = $value ? 1 : 0; } } elseif (is_bool($item) || is_numeric($item)) { $item = $item ? 1 : 0; } return $item; } /** * {@inheritDoc} */ public function getCreateTemporaryTableSnippetSQL() { return 'CREATE TABLE'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { return '#' . $tableName; } /** * {@inheritDoc} */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.000'; } /** * {@inheritDoc} */ public function getDateFormatString() { return 'Y-m-d H:i:s.000'; } /** * {@inheritDoc} */ public function getTimeFormatString() { return 'Y-m-d H:i:s.000'; } /** * {@inheritDoc} */ public function getDateTimeTzFormatString() { return $this->getDateTimeFormatString(); } /** * {@inheritDoc} */ public function getName() { return 'mssql'; } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'bigint' => 'bigint', 'numeric' => 'decimal', 'bit' => 'boolean', 'smallint' => 'smallint', 'decimal' => 'decimal', 'smallmoney' => 'integer', 'int' => 'integer', 'tinyint' => 'smallint', 'money' => 'integer', 'float' => 'float', 'real' => 'float', 'double' => 'float', 'double precision' => 'float', 'smalldatetime' => 'datetime', 'datetime' => 'datetime', 'char' => 'string', 'varchar' => 'string', 'text' => 'text', 'nchar' => 'string', 'nvarchar' => 'string', 'ntext' => 'text', 'binary' => 'binary', 'varbinary' => 'binary', 'image' => 'blob', 'uniqueidentifier' => 'guid', ]; } /** * {@inheritDoc} */ public function createSavePoint($savepoint) { return 'SAVE TRANSACTION ' . $savepoint; } /** * {@inheritDoc} */ public function releaseSavePoint($savepoint) { return ''; } /** * {@inheritDoc} */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TRANSACTION ' . $savepoint; } /** * {@inheritdoc} */ public function getForeignKeyReferentialActionSQL($action) { // RESTRICT is not supported, therefore falling back to NO ACTION. if (strtoupper($action) === 'RESTRICT') { return 'NO ACTION'; } return parent::getForeignKeyReferentialActionSQL($action); } /** * {@inheritDoc} */ public function appendLockHint($fromClause, $lockMode) { switch (true) { case $lockMode === LockMode::NONE: return $fromClause; case $lockMode === LockMode::PESSIMISTIC_READ: return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)'; case $lockMode === LockMode::PESSIMISTIC_WRITE: return $fromClause . ' WITH (UPDLOCK, ROWLOCK)'; default: return $fromClause; } } /** * {@inheritDoc} */ public function getForUpdateSQL() { return ' '; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLServerKeywords::class; } /** * {@inheritDoc} */ public function quoteSingleIdentifier($str) { return '[' . str_replace(']', ']]', $str) . ']'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'VARBINARY(MAX)'; } /** * {@inheritdoc} * * Modifies column declaration order as it differs in Microsoft SQL Server. */ public function getColumnDeclarationSQL($name, array $column) { if (isset($column['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($column); } else { $collation = isset($column['collation']) && $column['collation'] ? ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; $notnull = isset($column['notnull']) && $column['notnull'] ? ' NOT NULL' : ''; $unique = isset($column['unique']) && $column['unique'] ? ' ' . $this->getUniqueFieldDeclarationSQL() : ''; $check = isset($column['check']) && $column['check'] ? ' ' . $column['check'] : ''; $typeDecl = $column['type']->getSQLDeclaration($column, $this); $columnDef = $typeDecl . $collation . $notnull . $unique . $check; } return $name . ' ' . $columnDef; } /** * Returns a unique default constraint name for a table and column. * * @param string $table Name of the table to generate the unique default constraint name for. * @param string $column Name of the column in the table to generate the unique default constraint name for. * * @return string */ private function generateDefaultConstraintName($table, $column) { return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); } /** * Returns a hash value for a given identifier. * * @param string $identifier Identifier to generate a hash value for. * * @return string */ private function generateIdentifierName($identifier) { // Always generate name for unquoted identifiers to ensure consistency. $identifier = new Identifier($identifier); return strtoupper(dechex(crc32($identifier->getName()))); } protected function getCommentOnTableSQL(string $tableName, ?string $comment): string { return sprintf( " EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N%s, @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N%s ", $this->quoteStringLiteral((string) $comment), $this->quoteStringLiteral($tableName) ); } public function getListTableMetadataSQL(string $table): string { return sprintf( " SELECT p.value AS [table_comment] FROM sys.tables AS tbl INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 WHERE (tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description') ", $this->quoteStringLiteral($table) ); } } dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php 0000644 00000106725 15120025741 0016123 0 ustar 00 <?php namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Constraint; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\Types; use function array_merge; use function array_unique; use function array_values; use function implode; use function is_numeric; use function sprintf; use function sqrt; use function str_replace; use function strlen; use function strpos; use function strtolower; use function trim; /** * The SqlitePlatform class describes the specifics and dialects of the SQLite * database platform. * * @todo Rename: SQLitePlatform */ class SqlitePlatform extends AbstractPlatform { /** * {@inheritDoc} */ public function getRegexpExpression() { return 'REGEXP'; } /** * {@inheritDoc} * * @deprecated Use application-generated UUIDs instead */ public function getGuidExpression() { return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || " . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || " . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || " . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))"; } /** * @param string $type * * @return string */ public function getNowExpression($type = 'timestamp') { switch ($type) { case 'time': return 'time(\'now\')'; case 'date': return 'date(\'now\')'; case 'timestamp': default: return 'datetime(\'now\')'; } } /** * {@inheritDoc} */ public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) { $trimChar = $char !== false ? ', ' . $char : ''; switch ($mode) { case TrimMode::LEADING: $trimFn = 'LTRIM'; break; case TrimMode::TRAILING: $trimFn = 'RTRIM'; break; default: $trimFn = 'TRIM'; } return $trimFn . '(' . $str . $trimChar . ')'; } /** * {@inheritDoc} * * SQLite only supports the 2 parameter variant of this function */ public function getSubstringExpression($string, $start, $length = null) { if ($length !== null) { return 'SUBSTR(' . $string . ', ' . $start . ', ' . $length . ')'; } return 'SUBSTR(' . $string . ', ' . $start . ', LENGTH(' . $string . '))'; } /** * {@inheritDoc} */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos === false) { return 'LOCATE(' . $str . ', ' . $substr . ')'; } return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')'; } /** * {@inheritdoc} */ protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) { switch ($unit) { case DateIntervalUnit::SECOND: case DateIntervalUnit::MINUTE: case DateIntervalUnit::HOUR: return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } switch ($unit) { case DateIntervalUnit::WEEK: $interval *= 7; $unit = DateIntervalUnit::DAY; break; case DateIntervalUnit::QUARTER: $interval *= 3; $unit = DateIntervalUnit::MONTH; break; } if (! is_numeric($interval)) { $interval = "' || " . $interval . " || '"; } return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')"; } /** * {@inheritDoc} */ public function getDateDiffExpression($date1, $date2) { return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2); } /** * {@inheritDoc} */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case TransactionIsolationLevel::READ_UNCOMMITTED: return '0'; case TransactionIsolationLevel::READ_COMMITTED: case TransactionIsolationLevel::REPEATABLE_READ: case TransactionIsolationLevel::SERIALIZABLE: return '1'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * {@inheritDoc} */ public function getSetTransactionIsolationSQL($level) { return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); } /** * {@inheritDoc} */ public function prefersIdentityColumns() { return true; } /** * {@inheritDoc} */ public function getBooleanTypeDeclarationSQL(array $column) { return 'BOOLEAN'; } /** * {@inheritDoc} */ public function getIntegerTypeDeclarationSQL(array $column) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getBigIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @param array<string, mixed> $column * * @return string */ public function getTinyIntTypeDeclarationSql(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getSmallIntTypeDeclarationSQL(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * @param array<string, mixed> $column * * @return string */ public function getMediumIntTypeDeclarationSql(array $column) { // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT columns if (! empty($column['autoincrement'])) { return $this->getIntegerTypeDeclarationSQL($column); } return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); } /** * {@inheritDoc} */ public function getDateTimeTypeDeclarationSQL(array $column) { return 'DATETIME'; } /** * {@inheritDoc} */ public function getDateTypeDeclarationSQL(array $column) { return 'DATE'; } /** * {@inheritDoc} */ public function getTimeTypeDeclarationSQL(array $column) { return 'TIME'; } /** * {@inheritDoc} */ protected function _getCommonIntegerTypeDeclarationSQL(array $column) { // sqlite autoincrement is only possible for the primary key if (! empty($column['autoincrement'])) { return ' PRIMARY KEY AUTOINCREMENT'; } return ! empty($column['unsigned']) ? ' UNSIGNED' : ''; } /** * {@inheritDoc} */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( $foreignKey->getQuotedLocalColumns($this), str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)), $foreignKey->getQuotedForeignColumns($this), $foreignKey->getName(), $foreignKey->getOptions() )); } /** * {@inheritDoc} */ protected function _getCreateTableSQL($name, array $columns, array $options = []) { $name = str_replace('.', '__', $name); $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $name => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); } } $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options); if (isset($options['foreignKeys'])) { foreach ($options['foreignKeys'] as $foreignKey) { $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey); } } $tableComment = ''; if (isset($options['comment'])) { $comment = trim($options['comment'], " '"); $tableComment = $this->getInlineTableCommentSQL($comment); } $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')']; if (isset($options['alter']) && $options['alter'] === true) { return $query; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } if (isset($options['unique']) && ! empty($options['unique'])) { foreach ($options['unique'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } return $query; } /** * Generate a PRIMARY KEY definition if no autoincrement value is used * * @param mixed[][] $columns * @param mixed[] $options */ private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string { if (empty($options['primary'])) { return ''; } $keyColumns = array_unique(array_values($options['primary'])); foreach ($keyColumns as $keyColumn) { if (! empty($columns[$keyColumn]['autoincrement'])) { return ''; } } return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } /** * {@inheritDoc} */ protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) { return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); } /** * {@inheritdoc} */ protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) { return 'BLOB'; } /** * {@inheritdoc} */ public function getBinaryMaxLength() { return 0; } /** * {@inheritdoc} */ public function getBinaryDefaultLength() { return 0; } /** * {@inheritDoc} */ public function getClobTypeDeclarationSQL(array $column) { return 'CLOB'; } /** * {@inheritDoc} */ public function getListTableConstraintsSQL($table) { $table = str_replace('.', '__', $table); return sprintf( "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name", $this->quoteStringLiteral($table) ); } /** * {@inheritDoc} */ public function getListTableColumnsSQL($table, $database = null) { $table = str_replace('.', '__', $table); return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table)); } /** * {@inheritDoc} */ public function getListTableIndexesSQL($table, $database = null) { $table = str_replace('.', '__', $table); return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table)); } /** * {@inheritDoc} */ public function getListTablesSQL() { return 'SELECT name FROM sqlite_master' . " WHERE type = 'table'" . " AND name != 'sqlite_sequence'" . " AND name != 'geometry_columns'" . " AND name != 'spatial_ref_sys'" . ' UNION ALL SELECT name FROM sqlite_temp_master' . " WHERE type = 'table' ORDER BY name"; } /** * {@inheritDoc} */ public function getListViewsSQL($database) { return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; } /** * {@inheritDoc} */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * {@inheritDoc} */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * {@inheritDoc} */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) { $query .= ' NOT'; } $query .= ' DEFERRABLE'; $query .= ' INITIALLY'; if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) { $query .= ' DEFERRED'; } else { $query .= ' IMMEDIATE'; } return $query; } /** * {@inheritDoc} */ public function supportsIdentityColumns() { return true; } /** * {@inheritDoc} */ public function supportsColumnCollation() { return true; } /** * {@inheritDoc} */ public function supportsInlineColumnComments() { return true; } /** * {@inheritDoc} */ public function getName() { return 'sqlite'; } /** * {@inheritDoc} */ public function getTruncateTableSQL($tableName, $cascade = false) { $tableIdentifier = new Identifier($tableName); $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this)); return 'DELETE FROM ' . $tableName; } /** * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). * * @param int|float $value * * @return float */ public static function udfSqrt($value) { return sqrt($value); } /** * User-defined function for Sqlite that implements MOD(a, b). * * @param int $a * @param int $b * * @return int */ public static function udfMod($a, $b) { return $a % $b; } /** * @param string $str * @param string $substr * @param int $offset * * @return int */ public static function udfLocate($str, $substr, $offset = 0) { // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions. // So we have to make them compatible if an offset is given. if ($offset > 0) { $offset -= 1; } $pos = strpos($str, $substr, $offset); if ($pos !== false) { return $pos + 1; } return 0; } /** * {@inheritDoc} */ public function getForUpdateSQL() { return ''; } /** * {@inheritDoc} */ public function getInlineColumnCommentSQL($comment) { return '--' . str_replace("\n", "\n--", $comment) . "\n"; } private function getInlineTableCommentSQL(string $comment): string { return $this->getInlineColumnCommentSQL($comment); } /** * {@inheritDoc} */ protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = [ 'boolean' => 'boolean', 'tinyint' => 'boolean', 'smallint' => 'smallint', 'mediumint' => 'integer', 'int' => 'integer', 'integer' => 'integer', 'serial' => 'integer', 'bigint' => 'bigint', 'bigserial' => 'bigint', 'clob' => 'text', 'tinytext' => 'text', 'mediumtext' => 'text', 'longtext' => 'text', 'text' => 'text', 'varchar' => 'string', 'longvarchar' => 'string', 'varchar2' => 'string', 'nvarchar' => 'string', 'image' => 'string', 'ntext' => 'string', 'char' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'time' => 'time', 'float' => 'float', 'double' => 'float', 'double precision' => 'float', 'real' => 'float', 'decimal' => 'decimal', 'numeric' => 'decimal', 'blob' => 'blob', ]; } /** * {@inheritDoc} */ protected function getReservedKeywordsClass() { return Keywords\SQLiteKeywords::class; } /** * {@inheritDoc} */ protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) { if (! $diff->fromTable instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema' ); } $sql = []; foreach ($diff->fromTable->getIndexes() as $index) { if ($index->isPrimary()) { continue; } $sql[] = $this->getDropIndexSQL($index, $diff->name); } return $sql; } /** * {@inheritDoc} */ protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) { $fromTable = $diff->fromTable; if (! $fromTable instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema' ); } $sql = []; $tableName = $diff->getNewName(); if ($tableName === false) { $tableName = $diff->getName($this); } foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) { if ($index->isPrimary()) { continue; } $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this)); } return $sql; } /** * {@inheritDoc} */ protected function doModifyLimitQuery($query, $limit, $offset) { if ($limit === null && $offset > 0) { return $query . ' LIMIT -1 OFFSET ' . $offset; } return parent::doModifyLimitQuery($query, $limit, $offset); } /** * {@inheritDoc} */ public function getBlobTypeDeclarationSQL(array $column) { return 'BLOB'; } /** * {@inheritDoc} */ public function getTemporaryTableName($tableName) { $tableName = str_replace('.', '__', $tableName); return $tableName; } /** * {@inheritDoc} * * Sqlite Platform emulates schema by underscoring each dot and generating tables * into the default database. * * This hack is implemented to be able to use SQLite as testdriver when * using schema supporting databases. */ public function canEmulateSchemas() { return true; } /** * {@inheritDoc} */ public function supportsForeignKeyConstraints() { return false; } /** * {@inheritDoc} */ public function getCreatePrimaryKeySQL(Index $index, $table) { throw new Exception('Sqlite platform does not support alter primary key.'); } /** * {@inheritdoc} */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritdoc} */ public function getDropForeignKeySQL($foreignKey, $table) { throw new Exception('Sqlite platform does not support alter foreign key.'); } /** * {@inheritDoc} */ public function getCreateConstraintSQL(Constraint $constraint, $table) { throw new Exception('Sqlite platform does not support alter constraint.'); } /** * {@inheritDoc} * * @param int|null $createFlags */ public function getCreateTableSQL(Table $table, $createFlags = null) { $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS; return parent::getCreateTableSQL($table, $createFlags); } /** * @param string $table * @param string|null $database * * @return string */ public function getListTableForeignKeysSQL($table, $database = null) { $table = str_replace('.', '__', $table); return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table)); } /** * {@inheritDoc} */ public function getAlterTableSQL(TableDiff $diff) { $sql = $this->getSimpleAlterTableSQL($diff); if ($sql !== false) { return $sql; } $fromTable = $diff->fromTable; if (! $fromTable instanceof Table) { throw new Exception( 'Sqlite platform requires for alter table the table diff with reference to original table schema' ); } $table = clone $fromTable; $columns = []; $oldColumnNames = []; $newColumnNames = []; $columnSql = []; foreach ($table->getColumns() as $columnName => $column) { $columnName = strtolower($columnName); $columns[$columnName] = $column; $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); } foreach ($diff->removedColumns as $columnName => $column) { if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { continue; } $columnName = strtolower($columnName); if (! isset($columns[$columnName])) { continue; } unset( $columns[$columnName], $oldColumnNames[$columnName], $newColumnNames[$columnName] ); } foreach ($diff->renamedColumns as $oldColumnName => $column) { if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { continue; } $oldColumnName = strtolower($oldColumnName); if (isset($columns[$oldColumnName])) { unset($columns[$oldColumnName]); } $columns[strtolower($column->getName())] = $column; if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $column->getQuotedName($this); } foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { continue; } if (isset($columns[$oldColumnName])) { unset($columns[$oldColumnName]); } $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column; if (! isset($newColumnNames[$oldColumnName])) { continue; } $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this); } foreach ($diff->addedColumns as $columnName => $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $columns[strtolower($columnName)] = $column; } $sql = []; $tableSql = []; if (! $this->onSchemaAlterTable($diff, $tableSql)) { $dataTable = new Table('__temp__' . $table->getName()); $newTable = new Table( $table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff, $fromTable), $this->getForeignKeysInAlteredTable($diff, $fromTable), 0, $table->getOptions() ); $newTable->addOption('alter', true); $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); $sql[] = sprintf( 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this) ); $sql[] = $this->getDropTableSQL($fromTable); $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); $sql[] = sprintf( 'INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this) ); $sql[] = $this->getDropTableSQL($dataTable); $newName = $diff->getNewName(); if ($newName !== false) { $sql[] = sprintf( 'ALTER TABLE %s RENAME TO %s', $newTable->getQuotedName($this), $newName->getQuotedName($this) ); } $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); } return array_merge($sql, $tableSql, $columnSql); } /** * @return string[]|false */ private function getSimpleAlterTableSQL(TableDiff $diff) { // Suppress changes on integer type autoincrement columns. foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { if ( ! $columnDiff->fromColumn instanceof Column || ! $columnDiff->column instanceof Column || ! $columnDiff->column->getAutoincrement() || ! $columnDiff->column->getType() instanceof Types\IntegerType ) { continue; } if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) { unset($diff->changedColumns[$oldColumnName]); continue; } $fromColumnType = $columnDiff->fromColumn->getType(); if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) { continue; } unset($diff->changedColumns[$oldColumnName]); } if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes) || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes) || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes) || ! empty($diff->renamedIndexes) ) { return false; } $table = new Table($diff->name); $sql = []; $tableSql = []; $columnSql = []; foreach ($diff->addedColumns as $column) { if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { continue; } $definition = array_merge([ 'unique' => null, 'autoincrement' => null, 'default' => null, ], $column->toArray()); $type = $definition['type']; switch (true) { case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']: case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL(): case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL(): case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL(): return false; } $definition['name'] = $column->getQuotedName($this); if ($type instanceof Types\StringType && $definition['length'] === null) { $definition['length'] = 255; } $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($definition['name'], $definition); } if (! $this->onSchemaAlterTable($diff, $tableSql)) { if ($diff->newName !== false) { $newTable = new Identifier($diff->newName); $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this); } } return array_merge($sql, $tableSql, $columnSql); } /** * @return string[] */ private function getColumnNamesInAlteredTable(TableDiff $diff, Table $fromTable) { $columns = []; foreach ($fromTable->getColumns() as $columnName => $column) { $columns[strtolower($columnName)] = $column->getName(); } foreach ($diff->removedColumns as $columnName => $column) { $columnName = strtolower($columnName); if (! isset($columns[$columnName])) { continue; } unset($columns[$columnName]); } foreach ($diff->renamedColumns as $oldColumnName => $column) { $columnName = $column->getName(); $columns[strtolower($oldColumnName)] = $columnName; $columns[strtolower($columnName)] = $columnName; } foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { $columnName = $columnDiff->column->getName(); $columns[strtolower($oldColumnName)] = $columnName; $columns[strtolower($columnName)] = $columnName; } foreach ($diff->addedColumns as $column) { $columnName = $column->getName(); $columns[strtolower($columnName)] = $columnName; } return $columns; } /** * @return Index[] */ private function getIndexesInAlteredTable(TableDiff $diff, Table $fromTable) { $indexes = $fromTable->getIndexes(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($indexes as $key => $index) { foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) { if (strtolower($key) !== strtolower($oldIndexName)) { continue; } unset($indexes[$key]); } $changed = false; $indexColumns = []; foreach ($index->getColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($indexes[$key]); continue 2; } $indexColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $indexes[$key] = new Index( $index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags() ); } foreach ($diff->removedIndexes as $index) { $indexName = strtolower($index->getName()); if (! strlen($indexName) || ! isset($indexes[$indexName])) { continue; } unset($indexes[$indexName]); } foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) { $indexName = strtolower($index->getName()); if (strlen($indexName)) { $indexes[$indexName] = $index; } else { $indexes[] = $index; } } return $indexes; } /** * @return ForeignKeyConstraint[] */ private function getForeignKeysInAlteredTable(TableDiff $diff, Table $fromTable) { $foreignKeys = $fromTable->getForeignKeys(); $columnNames = $this->getColumnNamesInAlteredTable($diff, $fromTable); foreach ($foreignKeys as $key => $constraint) { $changed = false; $localColumns = []; foreach ($constraint->getLocalColumns() as $columnName) { $normalizedColumnName = strtolower($columnName); if (! isset($columnNames[$normalizedColumnName])) { unset($foreignKeys[$key]); continue 2; } $localColumns[] = $columnNames[$normalizedColumnName]; if ($columnName === $columnNames[$normalizedColumnName]) { continue; } $changed = true; } if (! $changed) { continue; } $foreignKeys[$key] = new ForeignKeyConstraint( $localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions() ); } foreach ($diff->removedForeignKeys as $constraint) { if (! $constraint instanceof ForeignKeyConstraint) { $constraint = new Identifier($constraint); } $constraintName = strtolower($constraint->getName()); if (! strlen($constraintName) || ! isset($foreignKeys[$constraintName])) { continue; } unset($foreignKeys[$constraintName]); } foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) { $constraintName = strtolower($constraint->getName()); if (strlen($constraintName)) { $foreignKeys[$constraintName] = $constraint; } else { $foreignKeys[] = $constraint; } } return $foreignKeys; } /** * @return Index[] */ private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $fromTable) { $primaryIndex = []; foreach ($this->getIndexesInAlteredTable($diff, $fromTable) as $index) { if (! $index->isPrimary()) { continue; } $primaryIndex = [$index->getName() => $index]; } return $primaryIndex; } } dbal/lib/Doctrine/DBAL/Platforms/TrimMode.php 0000644 00000000466 15120025741 0014670 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Platforms; final class TrimMode { public const UNSPECIFIED = 0; public const LEADING = 1; public const TRAILING = 2; public const BOTH = 3; /** * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Portability/Connection.php 0000644 00000006543 15120025741 0015604 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\Connection as BaseConnection; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\ForwardCompatibility; use PDO; use function func_get_args; use const CASE_LOWER; use const CASE_UPPER; /** * Portability wrapper for a Connection. */ class Connection extends BaseConnection { public const PORTABILITY_ALL = 255; public const PORTABILITY_NONE = 0; public const PORTABILITY_RTRIM = 1; public const PORTABILITY_EMPTY_TO_NULL = 4; public const PORTABILITY_FIX_CASE = 8; /**#@+ * * @deprecated Will be removed as internal implementation details. */ public const PORTABILITY_DB2 = 13; public const PORTABILITY_ORACLE = 9; public const PORTABILITY_POSTGRESQL = 13; public const PORTABILITY_SQLITE = 13; public const PORTABILITY_OTHERVENDORS = 12; public const PORTABILITY_DRIZZLE = 13; public const PORTABILITY_SQLANYWHERE = 13; public const PORTABILITY_SQLSRV = 13; /**#@-*/ /** @var int */ private $portability = self::PORTABILITY_NONE; /** @var int|null */ private $case; /** * {@inheritdoc} */ public function connect() { $ret = parent::connect(); if ($ret) { $params = $this->getParams(); if (isset($params['portability'])) { $this->portability = $params['portability'] = (new OptimizeFlags())( $this->getDatabasePlatform(), $params['portability'] ); } if (isset($params['fetch_case']) && $this->portability & self::PORTABILITY_FIX_CASE) { if ($this->_conn instanceof PDOConnection) { // make use of c-level support for case handling $this->_conn->setAttribute(PDO::ATTR_CASE, $params['fetch_case']); } else { $this->case = $params['fetch_case'] === ColumnCase::LOWER ? CASE_LOWER : CASE_UPPER; } } } return $ret; } /** * @return int */ public function getPortability() { return $this->portability; } /** * @return int|null */ public function getFetchCase() { return $this->case; } /** * {@inheritdoc} */ public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null) { $stmt = new Statement(parent::executeQuery($sql, $params, $types, $qcp), $this); $stmt->setFetchMode($this->defaultFetchMode); return new ForwardCompatibility\Result($stmt); } /** * {@inheritdoc} * * @param string $sql * * @return Statement */ public function prepare($sql) { $stmt = new Statement(parent::prepare($sql), $this); $stmt->setFetchMode($this->defaultFetchMode); return $stmt; } /** * {@inheritdoc} */ public function query() { $connection = $this->getWrappedConnection(); $stmt = $connection->query(...func_get_args()); $stmt = new Statement($stmt, $this); $stmt->setFetchMode($this->defaultFetchMode); return $stmt; } } dbal/lib/Doctrine/DBAL/Portability/OptimizeFlags.php 0000644 00000002433 15120025741 0016254 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Platforms\SQLAnywhere16Platform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; final class OptimizeFlags { /** * Platform-specific portability flags that need to be excluded from the user-provided mode * since the platform already operates in this mode to avoid unnecessary conversion overhead. * * @var array<string,int> */ private static $platforms = [ DB2Platform::class => 0, OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL, PostgreSQL94Platform::class => 0, SQLAnywhere16Platform::class => 0, SqlitePlatform::class => 0, SQLServer2012Platform::class => 0, ]; public function __invoke(AbstractPlatform $platform, int $flags): int { foreach (self::$platforms as $class => $mask) { if ($platform instanceof $class) { $flags &= ~$mask; break; } } return $flags; } } dbal/lib/Doctrine/DBAL/Portability/Statement.php 0000644 00000024104 15120025741 0015442 0 ustar 00 <?php namespace Doctrine\DBAL\Portability; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use IteratorAggregate; use PDO; use ReturnTypeWillChange; use function array_change_key_case; use function assert; use function is_string; use function rtrim; /** * Portability wrapper for a Statement. */ class Statement implements IteratorAggregate, DriverStatement, Result { /** @var int */ private $portability; /** @var DriverStatement|ResultStatement */ private $stmt; /** @var int|null */ private $case; /** @var int */ private $defaultFetchMode = FetchMode::MIXED; /** * Wraps <tt>Statement</tt> and applies portability measures. * * @param DriverStatement|ResultStatement $stmt */ public function __construct($stmt, Connection $conn) { $this->stmt = $stmt; $this->portability = $conn->getPortability(); $this->case = $conn->getFetchCase(); } /** * {@inheritdoc} */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { assert($this->stmt instanceof DriverStatement); return $this->stmt->bindParam($param, $variable, $type, $length); } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = ParameterType::STRING) { assert($this->stmt instanceof DriverStatement); return $this->stmt->bindValue($param, $value, $type); } /** * {@inheritdoc} * * @deprecated Use free() instead. */ public function closeCursor() { return $this->stmt->closeCursor(); } /** * {@inheritdoc} */ public function columnCount() { return $this->stmt->columnCount(); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorCode() { assert($this->stmt instanceof DriverStatement); return $this->stmt->errorCode(); } /** * {@inheritdoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { assert($this->stmt instanceof DriverStatement); return $this->stmt->errorInfo(); } /** * {@inheritdoc} */ public function execute($params = null) { assert($this->stmt instanceof DriverStatement); return $this->stmt->execute($params); } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { $this->defaultFetchMode = $fetchMode; return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3); } /** * {@inheritdoc} * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. */ #[ReturnTypeWillChange] public function getIterator() { return new StatementIterator($this); } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { $fetchMode = $fetchMode ?: $this->defaultFetchMode; $row = $this->stmt->fetch($fetchMode); $iterateRow = ( $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL | Connection::PORTABILITY_RTRIM) ) !== 0; $fixCase = $this->case !== null && ($fetchMode === FetchMode::ASSOCIATIVE || $fetchMode === FetchMode::MIXED) && ($this->portability & Connection::PORTABILITY_FIX_CASE); $row = $this->fixRow($row, $iterateRow, $fixCase); return $row; } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { $fetchMode = $fetchMode ?: $this->defaultFetchMode; if ($fetchArgument) { $rows = $this->stmt->fetchAll($fetchMode, $fetchArgument); } else { $rows = $this->stmt->fetchAll($fetchMode); } $fixCase = $this->case !== null && ($fetchMode === FetchMode::ASSOCIATIVE || $fetchMode === FetchMode::MIXED) && ($this->portability & Connection::PORTABILITY_FIX_CASE); return $this->fixResultSet($rows, $fixCase, $fetchMode !== FetchMode::COLUMN); } /** * {@inheritdoc} */ public function fetchNumeric() { if ($this->stmt instanceof Result) { $row = $this->stmt->fetchNumeric(); } else { $row = $this->stmt->fetch(FetchMode::NUMERIC); } return $this->fixResult($row, false); } /** * {@inheritdoc} */ public function fetchAssociative() { if ($this->stmt instanceof Result) { $row = $this->stmt->fetchAssociative(); } else { $row = $this->stmt->fetch(FetchMode::ASSOCIATIVE); } return $this->fixResult($row, true); } /** * {@inheritdoc} */ public function fetchOne() { if ($this->stmt instanceof Result) { $value = $this->stmt->fetchOne(); } else { $value = $this->stmt->fetch(FetchMode::COLUMN); } if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0 && $value === '') { $value = null; } elseif (($this->portability & Connection::PORTABILITY_RTRIM) !== 0 && is_string($value)) { $value = rtrim($value); } return $value; } /** * {@inheritdoc} */ public function fetchAllNumeric(): array { if ($this->stmt instanceof Result) { $data = $this->stmt->fetchAllNumeric(); } else { $data = $this->stmt->fetchAll(FetchMode::NUMERIC); } return $this->fixResultSet($data, false, true); } /** * {@inheritdoc} */ public function fetchAllAssociative(): array { if ($this->stmt instanceof Result) { $data = $this->stmt->fetchAllAssociative(); } else { $data = $this->stmt->fetchAll(FetchMode::ASSOCIATIVE); } return $this->fixResultSet($data, true, true); } /** * {@inheritdoc} */ public function fetchFirstColumn(): array { if ($this->stmt instanceof Result) { $data = $this->stmt->fetchFirstColumn(); } else { $data = $this->stmt->fetchAll(FetchMode::COLUMN); } return $this->fixResultSet($data, true, false); } public function free(): void { if ($this->stmt instanceof Result) { $this->stmt->free(); return; } $this->stmt->closeCursor(); } /** * @param mixed $result * * @return mixed */ private function fixResult($result, bool $fixCase) { $iterateRow = ( $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL | Connection::PORTABILITY_RTRIM) ) !== 0; $fixCase = $fixCase && $this->case !== null && ($this->portability & Connection::PORTABILITY_FIX_CASE) !== 0; return $this->fixRow($result, $iterateRow, $fixCase); } /** * @param array<int,mixed> $resultSet * * @return array<int,mixed> */ private function fixResultSet(array $resultSet, bool $fixCase, bool $isArray): array { $iterateRow = ( $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL | Connection::PORTABILITY_RTRIM) ) !== 0; $fixCase = $fixCase && $this->case !== null && ($this->portability & Connection::PORTABILITY_FIX_CASE) !== 0; if (! $iterateRow && ! $fixCase) { return $resultSet; } if (! $isArray) { foreach ($resultSet as $num => $value) { $resultSet[$num] = [$value]; } } foreach ($resultSet as $num => $row) { $resultSet[$num] = $this->fixRow($row, $iterateRow, $fixCase); } if (! $isArray) { foreach ($resultSet as $num => $row) { $resultSet[$num] = $row[0]; } } return $resultSet; } /** * @param mixed $row * @param bool $iterateRow * @param bool $fixCase * * @return mixed */ protected function fixRow($row, $iterateRow, $fixCase) { if (! $row) { return $row; } if ($fixCase) { assert($this->case !== null); $row = array_change_key_case($row, $this->case); } if ($iterateRow) { foreach ($row as $k => $v) { if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $v === '') { $row[$k] = null; } elseif (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($v)) { $row[$k] = rtrim($v); } } } return $row; } /** * {@inheritdoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { $value = $this->stmt->fetchColumn($columnIndex); if ($this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL | Connection::PORTABILITY_RTRIM)) { if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $value === '') { $value = null; } elseif (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($value)) { $value = rtrim($value); } } return $value; } /** * {@inheritdoc} */ public function rowCount() { assert($this->stmt instanceof DriverStatement); return $this->stmt->rowCount(); } } dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php 0000644 00000010462 15120025741 0020464 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Countable; use Doctrine\Deprecations\Deprecation; use ReturnTypeWillChange; use function array_merge; use function count; use function implode; /** * Composite expression is responsible to build a group of similar expression. */ class CompositeExpression implements Countable { /** * Constant that represents an AND composite expression. */ public const TYPE_AND = 'AND'; /** * Constant that represents an OR composite expression. */ public const TYPE_OR = 'OR'; /** * The instance type of composite expression. * * @var string */ private $type; /** * Each expression part of the composite expression. * * @var self[]|string[] */ private $parts = []; /** * @internal Use the and() / or() factory methods. * * @param string $type Instance type of composite expression. * @param self[]|string[] $parts Composition of expressions to be joined on composite expression. */ public function __construct($type, array $parts = []) { $this->type = $type; $this->addMultiple($parts); Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3864', 'Do not use CompositeExpression constructor directly, use static and() and or() factory methods.' ); } /** * @param self|string $part * @param self|string ...$parts */ public static function and($part, ...$parts): self { return new self(self::TYPE_AND, array_merge([$part], $parts)); } /** * @param self|string $part * @param self|string ...$parts */ public static function or($part, ...$parts): self { return new self(self::TYPE_OR, array_merge([$part], $parts)); } /** * Adds multiple parts to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param self[]|string[] $parts * * @return CompositeExpression */ public function addMultiple(array $parts = []) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::addMultiple() is deprecated, use CompositeExpression::with() instead.' ); foreach ($parts as $part) { $this->add($part); } return $this; } /** * Adds an expression to composite expression. * * @deprecated This class will be made immutable. Use with() instead. * * @param mixed $part * * @return CompositeExpression */ public function add($part) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3844', 'CompositeExpression::add() is deprecated, use CompositeExpression::with() instead.' ); if (empty($part)) { return $this; } if ($part instanceof self && count($part) === 0) { return $this; } $this->parts[] = $part; return $this; } /** * Returns a new CompositeExpression with the given parts added. * * @param self|string $part * @param self|string ...$parts */ public function with($part, ...$parts): self { $that = clone $this; $that->parts[] = $part; foreach ($parts as $part) { $that->parts[] = $part; } return $that; } /** * Retrieves the amount of expressions on composite expression. * * @return int */ #[ReturnTypeWillChange] public function count() { return count($this->parts); } /** * Retrieves the string representation of this composite expression. * * @return string */ public function __toString() { if ($this->count() === 1) { return (string) $this->parts[0]; } return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; } /** * Returns the type of this composite expression (AND/OR). * * @return string */ public function getType() { return $this->type; } } dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php 0000644 00000022460 15120025741 0020111 0 ustar 00 <?php namespace Doctrine\DBAL\Query\Expression; use Doctrine\DBAL\Connection; use Doctrine\Deprecations\Deprecation; use function func_get_arg; use function func_get_args; use function func_num_args; use function implode; use function sprintf; /** * ExpressionBuilder class is responsible to dynamically create SQL query parts. */ class ExpressionBuilder { public const EQ = '='; public const NEQ = '<>'; public const LT = '<'; public const LTE = '<='; public const GT = '>'; public const GTE = '>='; /** * The DBAL Connection. * * @var Connection */ private $connection; /** * Initializes a new <tt>ExpressionBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Creates a conjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function and($expression, ...$expressions): CompositeExpression { return CompositeExpression::and($expression, ...$expressions); } /** * Creates a disjunction of the given expressions. * * @param string|CompositeExpression $expression * @param string|CompositeExpression ...$expressions */ public function or($expression, ...$expressions): CompositeExpression { return CompositeExpression::or($expression, ...$expressions); } /** * @deprecated Use `and()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function andX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::andX() is deprecated, use ExpressionBuilder::and() instead.' ); return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); } /** * @deprecated Use `or()` instead. * * @param mixed $x Optional clause. Defaults = null, but requires * at least one defined when converting to string. * * @return CompositeExpression */ public function orX($x = null) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3851', 'ExpressionBuilder::orX() is deprecated, use ExpressionBuilder::or() instead.' ); return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); } /** * Creates a comparison expression. * * @param mixed $x The left expression. * @param string $operator One of the ExpressionBuilder::* constants. * @param mixed $y The right expression. * * @return string */ public function comparison($x, $operator, $y) { return $x . ' ' . $operator . ' ' . $y; } /** * Creates an equality comparison expression with the given arguments. * * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> = <right expr>. Example: * * [php] * // u.id = ? * $expr->eq('u.id', '?'); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function eq($x, $y) { return $this->comparison($x, self::EQ, $y); } /** * Creates a non equality comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <> <right expr>. Example: * * [php] * // u.id <> 1 * $q->where($q->expr()->neq('u.id', '1')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function neq($x, $y) { return $this->comparison($x, self::NEQ, $y); } /** * Creates a lower-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> < <right expr>. Example: * * [php] * // u.id < ? * $q->where($q->expr()->lt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lt($x, $y) { return $this->comparison($x, self::LT, $y); } /** * Creates a lower-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> <= <right expr>. Example: * * [php] * // u.id <= ? * $q->where($q->expr()->lte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function lte($x, $y) { return $this->comparison($x, self::LTE, $y); } /** * Creates a greater-than comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> > <right expr>. Example: * * [php] * // u.id > ? * $q->where($q->expr()->gt('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gt($x, $y) { return $this->comparison($x, self::GT, $y); } /** * Creates a greater-than-equal comparison expression with the given arguments. * First argument is considered the left expression and the second is the right expression. * When converted to string, it will generated a <left expr> >= <right expr>. Example: * * [php] * // u.id >= ? * $q->where($q->expr()->gte('u.id', '?')); * * @param mixed $x The left expression. * @param mixed $y The right expression. * * @return string */ public function gte($x, $y) { return $this->comparison($x, self::GTE, $y); } /** * Creates an IS NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NULL. * * @return string */ public function isNull($x) { return $x . ' IS NULL'; } /** * Creates an IS NOT NULL expression with the given arguments. * * @param string $x The expression to be restricted by IS NOT NULL. * * @return string */ public function isNotNull($x) { return $x . ' IS NOT NULL'; } /** * Creates a LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * * @return string */ public function like($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates a NOT LIKE() comparison expression with the given arguments. * * @param string $x Field in string format to be inspected by NOT LIKE() comparison. * @param mixed $y Argument to be used in NOT LIKE() comparison. * * @return string */ public function notLike($x, $y/*, ?string $escapeChar = null */) { return $this->comparison($x, 'NOT LIKE', $y) . (func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : ''); } /** * Creates a IN () comparison expression with the given arguments. * * @param string $x The field in string format to be inspected by IN() comparison. * @param string|string[] $y The placeholder or the array of values to be used by IN() comparison. * * @return string */ public function in($x, $y) { return $this->comparison($x, 'IN', '(' . implode(', ', (array) $y) . ')'); } /** * Creates a NOT IN () comparison expression with the given arguments. * * @param string $x The expression to be inspected by NOT IN() comparison. * @param string|string[] $y The placeholder or the array of values to be used by NOT IN() comparison. * * @return string */ public function notIn($x, $y) { return $this->comparison($x, 'NOT IN', '(' . implode(', ', (array) $y) . ')'); } /** * Quotes a given input parameter. * * @param mixed $input The parameter to be quoted. * @param int|null $type The type of the parameter. * * @return string */ public function literal($input, $type = null) { return $this->connection->quote($input, $type); } } dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php 0000644 00000123465 15120025741 0014727 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ForwardCompatibility; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_filter; use function array_key_exists; use function array_keys; use function array_unshift; use function count; use function func_get_args; use function func_num_args; use function implode; use function is_array; use function is_object; use function key; use function strtoupper; use function substr; /** * QueryBuilder class is responsible to dynamically create SQL queries. * * Important: Verify that every feature you use will work with your database vendor. * SQL Query Builder does not attempt to validate the generated SQL at all. * * The query builder does no validation whatsoever if certain features even work with the * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements * even if some vendors such as MySQL support it. */ class QueryBuilder { /* * The query types. */ public const SELECT = 0; public const DELETE = 1; public const UPDATE = 2; public const INSERT = 3; /* * The builder states. */ public const STATE_DIRTY = 0; public const STATE_CLEAN = 1; /** * The DBAL Connection. * * @var Connection */ private $connection; /* * The default values of SQL parts collection */ private const SQL_PARTS_DEFAULTS = [ 'select' => [], 'distinct' => false, 'from' => [], 'join' => [], 'set' => [], 'where' => null, 'groupBy' => [], 'having' => null, 'orderBy' => [], 'values' => [], ]; /** * The array of SQL parts collected. * * @var mixed[] */ private $sqlParts = self::SQL_PARTS_DEFAULTS; /** * The complete SQL string for this query. * * @var string|null */ private $sql; /** * The query parameters. * * @var array<int, mixed>|array<string, mixed> */ private $params = []; /** * The parameter type map of this query. * * @var array<int, int|string|Type|null>|array<string, int|string|Type|null> */ private $paramTypes = []; /** * The type of query this is. Can be select, update or delete. * * @var int */ private $type = self::SELECT; /** * The state of the query object. Can be dirty or clean. * * @var int */ private $state = self::STATE_CLEAN; /** * The index of the first result to retrieve. * * @var int */ private $firstResult = 0; /** * The maximum number of results to retrieve or NULL to retrieve all results. * * @var int|null */ private $maxResults; /** * The counter of bound parameters used with {@see bindValue). * * @var int */ private $boundCounter = 0; /** * Initializes a new <tt>QueryBuilder</tt>. * * @param Connection $connection The DBAL Connection. */ public function __construct(Connection $connection) { $this->connection = $connection; } /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. * This producer method is intended for convenient inline usage. Example: * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where($qb->expr()->eq('u.id', 1)); * </code> * * For more complex expression construction, consider storing the expression * builder object in a local variable. * * @return ExpressionBuilder */ public function expr() { return $this->connection->getExpressionBuilder(); } /** * Gets the type of the currently built query. * * @return int */ public function getType() { return $this->type; } /** * Gets the associated DBAL Connection for this query builder. * * @return Connection */ public function getConnection() { return $this->connection; } /** * Gets the state of this query builder instance. * * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. */ public function getState() { return $this->state; } /** * Executes this query using the bound parameters and their types. * * @return ForwardCompatibility\Result|int|string * * @throws Exception */ public function execute() { if ($this->type === self::SELECT) { return ForwardCompatibility\Result::ensure( $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes) ); } return $this->connection->executeStatement($this->getSQL(), $this->params, $this->paramTypes); } /** * Gets the complete SQL string formed by the current specifications of this QueryBuilder. * * <code> * $qb = $em->createQueryBuilder() * ->select('u') * ->from('User', 'u') * echo $qb->getSQL(); // SELECT u FROM User u * </code> * * @return string The SQL query string. */ public function getSQL() { if ($this->sql !== null && $this->state === self::STATE_CLEAN) { return $this->sql; } switch ($this->type) { case self::INSERT: $sql = $this->getSQLForInsert(); break; case self::DELETE: $sql = $this->getSQLForDelete(); break; case self::UPDATE: $sql = $this->getSQLForUpdate(); break; case self::SELECT: default: $sql = $this->getSQLForSelect(); break; } $this->state = self::STATE_CLEAN; $this->sql = $sql; return $sql; } /** * Sets a query parameter for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id') * ->setParameter(':user_id', 1); * </code> * * @param int|string $key Parameter position or name * @param mixed $value Parameter value * @param int|string|Type|null $type Parameter type * * @return $this This QueryBuilder instance. */ public function setParameter($key, $value, $type = null) { if ($type !== null) { $this->paramTypes[$key] = $type; } $this->params[$key] = $value; return $this; } /** * Sets a collection of query parameters for the query being constructed. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.id = :user_id1 OR u.id = :user_id2') * ->setParameters(array( * ':user_id1' => 1, * ':user_id2' => 2 * )); * </code> * * @param array<int, mixed>|array<string, mixed> $params Parameters to set * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return $this This QueryBuilder instance. */ public function setParameters(array $params, array $types = []) { $this->paramTypes = $types; $this->params = $params; return $this; } /** * Gets all defined query parameters for the query being constructed indexed by parameter index or name. * * @return array<int, mixed>|array<string, mixed> The currently defined query parameters */ public function getParameters() { return $this->params; } /** * Gets a (previously set) query parameter of the query being constructed. * * @param mixed $key The key (index or name) of the bound parameter. * * @return mixed The value of the bound parameter. */ public function getParameter($key) { return $this->params[$key] ?? null; } /** * Gets all defined query parameter types for the query being constructed indexed by parameter index or name. * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> The currently defined * query parameter types */ public function getParameterTypes() { return $this->paramTypes; } /** * Gets a (previously set) query parameter type of the query being constructed. * * @param int|string $key The key of the bound parameter type * * @return int|string|Type|null The value of the bound parameter type */ public function getParameterType($key) { return $this->paramTypes[$key] ?? null; } /** * Sets the position of the first result to retrieve (the "offset"). * * @param int $firstResult The first result to return. * * @return $this This QueryBuilder instance. */ public function setFirstResult($firstResult) { $this->state = self::STATE_DIRTY; $this->firstResult = $firstResult; return $this; } /** * Gets the position of the first result the query object was set to retrieve (the "offset"). * * @return int The position of the first result. */ public function getFirstResult() { return $this->firstResult; } /** * Sets the maximum number of results to retrieve (the "limit"). * * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results. * * @return $this This QueryBuilder instance. */ public function setMaxResults($maxResults) { $this->state = self::STATE_DIRTY; $this->maxResults = $maxResults; return $this; } /** * Gets the maximum number of results the query object was set to retrieve (the "limit"). * Returns NULL if all results will be returned. * * @return int|null The maximum number of results. */ public function getMaxResults() { return $this->maxResults; } /** * Either appends to or replaces a single, generic query part. * * The available parts are: 'select', 'from', 'set', 'where', * 'groupBy', 'having' and 'orderBy'. * * @param string $sqlPartName * @param mixed $sqlPart * @param bool $append * * @return $this This QueryBuilder instance. */ public function add($sqlPartName, $sqlPart, $append = false) { $isArray = is_array($sqlPart); $isMultiple = is_array($this->sqlParts[$sqlPartName]); if ($isMultiple && ! $isArray) { $sqlPart = [$sqlPart]; } $this->state = self::STATE_DIRTY; if ($append) { if ( $sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set' ) { foreach ($sqlPart as $part) { $this->sqlParts[$sqlPartName][] = $part; } } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) { $key = key($sqlPart); $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; } elseif ($isMultiple) { $this->sqlParts[$sqlPartName][] = $sqlPart; } else { $this->sqlParts[$sqlPartName] = $sqlPart; } return $this; } $this->sqlParts[$sqlPartName] = $sqlPart; return $this; } /** * Specifies an item that is to be returned in the query result. * Replaces any previously specified selections, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id', 'p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function select($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if (empty($select)) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::select is deprecated, ' . 'pass each value as an individual variadic argument instead.' ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects); } /** * Adds DISTINCT to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->distinct() * ->from('users', 'u') * </code> * * @return $this This QueryBuilder instance. */ public function distinct(): self { $this->sqlParts['distinct'] = true; return $this; } /** * Adds an item that is to be returned in the query result. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->addSelect('p.id') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); * </code> * * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addSelect($select = null/*, string ...$selects*/) { $this->type = self::SELECT; if (empty($select)) { return $this; } if (is_array($select)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addSelect is deprecated, ' . 'pass each value as an individual variadic argument instead.' ); } $selects = is_array($select) ? $select : func_get_args(); return $this->add('select', $selects, true); } /** * Turns the query being built into a bulk delete query that ranges over * a certain table. * * <code> * $qb = $conn->createQueryBuilder() * ->delete('users', 'u') * ->where('u.id = :user_id') * ->setParameter(':user_id', 1); * </code> * * @param string $delete The table whose rows are subject to the deletion. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function delete($delete = null, $alias = null) { $this->type = self::DELETE; if (! $delete) { return $this; } return $this->add('from', [ 'table' => $delete, 'alias' => $alias, ]); } /** * Turns the query being built into a bulk update query that ranges over * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $update The table whose rows are subject to the update. * @param string $alias The table alias used in the constructed query. * * @return $this This QueryBuilder instance. */ public function update($update = null, $alias = null) { $this->type = self::UPDATE; if (! $update) { return $this; } return $this->add('from', [ 'table' => $update, 'alias' => $alias, ]); } /** * Turns the query being built into an insert query that inserts into * a certain table * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param string $insert The table into which the rows should be inserted. * * @return $this This QueryBuilder instance. */ public function insert($insert = null) { $this->type = self::INSERT; if (! $insert) { return $this; } return $this->add('from', ['table' => $insert]); } /** * Creates and adds a query root corresponding to the table identified by the * given alias, forming a cartesian product with any existing query roots. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.id') * ->from('users', 'u') * </code> * * @param string $from The table. * @param string|null $alias The alias of the table. * * @return $this This QueryBuilder instance. */ public function from($from, $alias = null) { return $this->add('from', [ 'table' => $from, 'alias' => $alias, ], true); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function join($fromAlias, $join, $alias, $condition = null) { return $this->innerJoin($fromAlias, $join, $alias, $condition); } /** * Creates and adds a join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function innerJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'inner', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a left join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function leftJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'left', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Creates and adds a right join to the query. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); * </code> * * @param string $fromAlias The alias that points to a from clause. * @param string $join The table name to join. * @param string $alias The alias of the join table. * @param string $condition The condition for the join. * * @return $this This QueryBuilder instance. */ public function rightJoin($fromAlias, $join, $alias, $condition = null) { return $this->add('join', [ $fromAlias => [ 'joinType' => 'right', 'joinTable' => $join, 'joinAlias' => $alias, 'joinCondition' => $condition, ], ], true); } /** * Sets a new value for a column in a bulk update query. * * <code> * $qb = $conn->createQueryBuilder() * ->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where('c.id = ?'); * </code> * * @param string $key The column to set. * @param string $value The value, expression, placeholder, etc. * * @return $this This QueryBuilder instance. */ public function set($key, $value) { return $this->add('set', $key . ' = ' . $value, true); } /** * Specifies one or more restrictions to the query result. * Replaces any previously specified restrictions, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->select('c.value') * ->from('counters', 'c') * ->where('c.id = ?'); * * // You can optionally programatically build and/or expressions * $qb = $conn->createQueryBuilder(); * * $or = $qb->expr()->orx(); * $or->add($qb->expr()->eq('c.id', 1)); * $or->add($qb->expr()->eq('c.id', 2)); * * $qb->update('counters', 'c') * ->set('c.value', 'c.value + 1') * ->where($or); * </code> * * @param mixed $predicates The restriction predicates. * * @return $this This QueryBuilder instance. */ public function where($predicates) { if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) { $predicates = CompositeExpression::and(...func_get_args()); } return $this->add('where', $predicates); } /** * Adds one or more restrictions to the query results, forming a logical * conjunction with any previously specified restrictions. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u') * ->from('users', 'u') * ->where('u.username LIKE ?') * ->andWhere('u.is_active = 1'); * </code> * * @see where() * * @param mixed $where The query restrictions. * * @return $this This QueryBuilder instance. */ public function andWhere($where) { $args = func_get_args(); $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { if (count($args) > 0) { $where = $where->with(...$args); } } else { array_unshift($args, $where); $where = CompositeExpression::and(...$args); } return $this->add('where', $where, true); } /** * Adds one or more restrictions to the query results, forming a logical * disjunction with any previously specified restrictions. * * <code> * $qb = $em->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->where('u.id = 1') * ->orWhere('u.id = 2'); * </code> * * @see where() * * @param mixed $where The WHERE statement. * * @return $this This QueryBuilder instance. */ public function orWhere($where) { $args = func_get_args(); $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { if (count($args) > 0) { $where = $where->with(...$args); } } else { array_unshift($args, $where); $where = CompositeExpression::or(...$args); } return $this->add('where', $where, true); } /** * Specifies a grouping over the results of the query. * Replaces any previously specified groupings, if any. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.id'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function groupBy($groupBy/*, string ...$groupBys*/) { if (empty($groupBy)) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::groupBy is deprecated, ' . 'pass each value as an individual variadic argument instead.' ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, false); } /** * Adds a grouping expression to the query. * * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument. * * <code> * $qb = $conn->createQueryBuilder() * ->select('u.name') * ->from('users', 'u') * ->groupBy('u.lastLogin') * ->addGroupBy('u.createdAt'); * </code> * * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED. * Pass each value as an individual argument. * * @return $this This QueryBuilder instance. */ public function addGroupBy($groupBy/*, string ...$groupBys*/) { if (empty($groupBy)) { return $this; } if (is_array($groupBy)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3837', 'Passing an array for the first argument to QueryBuilder::addGroupBy is deprecated, ' . 'pass each value as an individual variadic argument instead.' ); } $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); return $this->add('groupBy', $groupBy, true); } /** * Sets a value for a column in an insert query. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?' * ) * ) * ->setValue('password', '?'); * </code> * * @param string $column The column into which the value should be inserted. * @param string $value The value that should be inserted into the column. * * @return $this This QueryBuilder instance. */ public function setValue($column, $value) { $this->sqlParts['values'][$column] = $value; return $this; } /** * Specifies values for an insert query indexed by column names. * Replaces any previous values, if any. * * <code> * $qb = $conn->createQueryBuilder() * ->insert('users') * ->values( * array( * 'name' => '?', * 'password' => '?' * ) * ); * </code> * * @param mixed[] $values The values to specify for the insert query indexed by column names. * * @return $this This QueryBuilder instance. */ public function values(array $values) { return $this->add('values', $values); } /** * Specifies a restriction over the groups of the query. * Replaces any previous having restrictions, if any. * * @param mixed $having The restriction over the groups. * * @return $this This QueryBuilder instance. */ public function having($having) { if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) { $having = CompositeExpression::and(...func_get_args()); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * conjunction with any existing having restrictions. * * @param mixed $having The restriction to append. * * @return $this This QueryBuilder instance. */ public function andHaving($having) { $args = func_get_args(); $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::and(...$args); } return $this->add('having', $having); } /** * Adds a restriction over the groups of the query, forming a logical * disjunction with any existing having restrictions. * * @param mixed $having The restriction to add. * * @return $this This QueryBuilder instance. */ public function orHaving($having) { $args = func_get_args(); $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { $having = $having->with(...$args); } else { array_unshift($args, $having); $having = CompositeExpression::or(...$args); } return $this->add('having', $having); } /** * Specifies an ordering for the query results. * Replaces any previously specified orderings, if any. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function orderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false); } /** * Adds an ordering to the query results. * * @param string $sort The ordering expression. * @param string $order The ordering direction. * * @return $this This QueryBuilder instance. */ public function addOrderBy($sort, $order = null) { return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true); } /** * Gets a query part by its name. * * @param string $queryPartName * * @return mixed */ public function getQueryPart($queryPartName) { return $this->sqlParts[$queryPartName]; } /** * Gets all query parts. * * @return mixed[] */ public function getQueryParts() { return $this->sqlParts; } /** * Resets SQL parts. * * @param string[]|null $queryPartNames * * @return $this This QueryBuilder instance. */ public function resetQueryParts($queryPartNames = null) { if ($queryPartNames === null) { $queryPartNames = array_keys($this->sqlParts); } foreach ($queryPartNames as $queryPartName) { $this->resetQueryPart($queryPartName); } return $this; } /** * Resets a single SQL part. * * @param string $queryPartName * * @return $this This QueryBuilder instance. */ public function resetQueryPart($queryPartName) { $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName]; $this->state = self::STATE_DIRTY; return $this; } /** * @return string * * @throws QueryException */ private function getSQLForSelect() { $query = 'SELECT ' . ($this->sqlParts['distinct'] ? 'DISTINCT ' : '') . implode(', ', $this->sqlParts['select']); $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '') . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '') . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '') . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '') . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : ''); if ($this->isLimitQuery()) { return $this->connection->getDatabasePlatform()->modifyLimitQuery( $query, $this->maxResults, $this->firstResult ); } return $query; } /** * @return string[] */ private function getFromClauses() { $fromClauses = []; $knownAliases = []; // Loop through all FROM clauses foreach ($this->sqlParts['from'] as $from) { if ($from['alias'] === null) { $tableSql = $from['table']; $tableReference = $from['table']; } else { $tableSql = $from['table'] . ' ' . $from['alias']; $tableReference = $from['alias']; } $knownAliases[$tableReference] = true; $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases); } $this->verifyAllAliasesAreKnown($knownAliases); return $fromClauses; } /** * @param array<string,true> $knownAliases * * @throws QueryException */ private function verifyAllAliasesAreKnown(array $knownAliases): void { foreach ($this->sqlParts['join'] as $fromAlias => $joins) { if (! isset($knownAliases[$fromAlias])) { throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); } } } /** * @return bool */ private function isLimitQuery() { return $this->maxResults !== null || $this->firstResult !== 0; } /** * Converts this instance into an INSERT string in SQL. * * @return string */ private function getSQLForInsert() { return 'INSERT INTO ' . $this->sqlParts['from']['table'] . ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' . ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')'; } /** * Converts this instance into an UPDATE string in SQL. * * @return string */ private function getSQLForUpdate() { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'UPDATE ' . $table . ' SET ' . implode(', ', $this->sqlParts['set']) . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Converts this instance into a DELETE string in SQL. * * @return string */ private function getSQLForDelete() { $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); } /** * Gets a string representation of this QueryBuilder which corresponds to * the final SQL query being constructed. * * @return string The string representation of this QueryBuilder. */ public function __toString() { return $this->getSQL(); } /** * Creates a new named parameter and bind the value $value to it. * * This method provides a shortcut for PDOStatement::bindValue * when using prepared statements. * * The parameter $value specifies the value that you want to bind. If * $placeholder is not provided bindValue() will automatically create a * placeholder for you. An automatic placeholder will be of the name * ':dcValue1', ':dcValue2' etc. * * For more information see {@link http://php.net/pdostatement-bindparam} * * Example: * <code> * $value = 2; * $q->eq( 'id', $q->bindValue( $value ) ); * $stmt = $q->executeQuery(); // executed with 'id = 2' * </code> * * @link http://www.zetacomponents.org * * @param mixed $value * @param int|string|Type|null $type * @param string $placeHolder The name to bind with. The string must start with a colon ':'. * * @return string the placeholder name used. */ public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null) { if ($placeHolder === null) { $this->boundCounter++; $placeHolder = ':dcValue' . $this->boundCounter; } $this->setParameter(substr($placeHolder, 1), $value, $type); return $placeHolder; } /** * Creates a new positional parameter and bind the given value to it. * * Attention: If you are using positional parameters with the query builder you have * to be very careful to bind all parameters in the order they appear in the SQL * statement , otherwise they get bound in the wrong order which can lead to serious * bugs in your code. * * Example: * <code> * $qb = $conn->createQueryBuilder(); * $qb->select('u.*') * ->from('users', 'u') * ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING)) * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING)) * </code> * * @param mixed $value * @param int|string|Type|null $type * * @return string */ public function createPositionalParameter($value, $type = ParameterType::STRING) { $this->boundCounter++; $this->setParameter($this->boundCounter, $value, $type); return '?'; } /** * @param string $fromAlias * @param array<string,true> $knownAliases * * @return string * * @throws QueryException */ private function getSQLForJoins($fromAlias, array &$knownAliases) { $sql = ''; if (isset($this->sqlParts['join'][$fromAlias])) { foreach ($this->sqlParts['join'][$fromAlias] as $join) { if (array_key_exists($join['joinAlias'], $knownAliases)) { throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases)); } $sql .= ' ' . strtoupper($join['joinType']) . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']; if ($join['joinCondition'] !== null) { $sql .= ' ON ' . $join['joinCondition']; } $knownAliases[$join['joinAlias']] = true; } foreach ($this->sqlParts['join'][$fromAlias] as $join) { $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); } } return $sql; } /** * Deep clone of all expression objects in the SQL parts. * * @return void */ public function __clone() { foreach ($this->sqlParts as $part => $elements) { if (is_array($this->sqlParts[$part])) { foreach ($this->sqlParts[$part] as $idx => $element) { if (! is_object($element)) { continue; } $this->sqlParts[$part][$idx] = clone $element; } } elseif (is_object($elements)) { $this->sqlParts[$part] = clone $elements; } } foreach ($this->params as $name => $param) { if (! is_object($param)) { continue; } $this->params[$name] = clone $param; } } } dbal/lib/Doctrine/DBAL/Query/QueryException.php 0000644 00000001773 15120025741 0015274 0 ustar 00 <?php namespace Doctrine\DBAL\Query; use Doctrine\DBAL\Exception; use function implode; /** * @psalm-immutable */ class QueryException extends Exception { /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function unknownAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not part of " . 'any FROM or JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } /** * @param string $alias * @param string[] $registeredAliases * * @return QueryException */ public static function nonUniqueAlias($alias, $registeredAliases) { return new self("The given alias '" . $alias . "' is not unique " . 'in FROM and JOIN clause table. The currently registered ' . 'aliases are: ' . implode(', ', $registeredAliases) . '.'); } } dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php 0000644 00000002234 15120025741 0022373 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Synchronizer; use Doctrine\DBAL\Connection; use Doctrine\Deprecations\Deprecation; use Throwable; /** * Abstract schema synchronizer with methods for executing batches of SQL. * * @deprecated */ abstract class AbstractSchemaSynchronizer implements SchemaSynchronizer { /** @var Connection */ protected $conn; public function __construct(Connection $conn) { $this->conn = $conn; Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4213', 'SchemaSynchronizer API is deprecated without a replacement and will be removed in DBAL 3.0' ); } /** * @param string[] $sql * * @return void */ protected function processSqlSafely(array $sql) { foreach ($sql as $s) { try { $this->conn->exec($s); } catch (Throwable $e) { } } } /** * @param string[] $sql * * @return void */ protected function processSql(array $sql) { foreach ($sql as $s) { $this->conn->exec($s); } } } dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php 0000644 00000003104 15120025741 0020704 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Synchronizer; use Doctrine\DBAL\Schema\Schema; /** * The synchronizer knows how to synchronize a schema with the configured * database. * * @deprecated */ interface SchemaSynchronizer { /** * Gets the SQL statements that can be executed to create the schema. * * @return string[] */ public function getCreateSchema(Schema $createSchema); /** * Gets the SQL Statements to update given schema with the underlying db. * * @param bool $noDrops * * @return string[] */ public function getUpdateSchema(Schema $toSchema, $noDrops = false); /** * Gets the SQL Statements to drop the given schema from underlying db. * * @return string[] */ public function getDropSchema(Schema $dropSchema); /** * Gets the SQL statements to drop all schema assets from underlying db. * * @return string[] */ public function getDropAllSchema(); /** * Creates the Schema. * * @return void */ public function createSchema(Schema $createSchema); /** * Updates the Schema to new schema version. * * @param bool $noDrops * * @return void */ public function updateSchema(Schema $toSchema, $noDrops = false); /** * Drops the given database schema from the underlying db. * * @return void */ public function dropSchema(Schema $dropSchema); /** * Drops all assets from the underlying db. * * @return void */ public function dropAllSchema(); } dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php 0000644 00000007601 15120025741 0022360 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Synchronizer; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; use function count; /** * Schema Synchronizer for Default DBAL Connection. * * @deprecated */ class SingleDatabaseSynchronizer extends AbstractSchemaSynchronizer { /** @var AbstractPlatform */ private $platform; public function __construct(Connection $conn) { parent::__construct($conn); $this->platform = $conn->getDatabasePlatform(); } /** * {@inheritdoc} */ public function getCreateSchema(Schema $createSchema) { return $createSchema->toSql($this->platform); } /** * {@inheritdoc} */ public function getUpdateSchema(Schema $toSchema, $noDrops = false) { $comparator = new Comparator(); $sm = $this->conn->getSchemaManager(); $fromSchema = $sm->createSchema(); $schemaDiff = $comparator->compare($fromSchema, $toSchema); if ($noDrops) { return $schemaDiff->toSaveSql($this->platform); } return $schemaDiff->toSql($this->platform); } /** * {@inheritdoc} */ public function getDropSchema(Schema $dropSchema) { $visitor = new DropSchemaSqlCollector($this->platform); $sm = $this->conn->getSchemaManager(); $fullSchema = $sm->createSchema(); foreach ($fullSchema->getTables() as $table) { if ($dropSchema->hasTable($table->getName())) { $visitor->acceptTable($table); } foreach ($table->getForeignKeys() as $foreignKey) { if (! $dropSchema->hasTable($table->getName())) { continue; } if (! $dropSchema->hasTable($foreignKey->getForeignTableName())) { continue; } $visitor->acceptForeignKey($table, $foreignKey); } } if (! $this->platform->supportsSequences()) { return $visitor->getQueries(); } foreach ($dropSchema->getSequences() as $sequence) { $visitor->acceptSequence($sequence); } foreach ($dropSchema->getTables() as $table) { $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { continue; } $columns = $primaryKey->getColumns(); if (count($columns) > 1) { continue; } $checkSequence = $table->getName() . '_' . $columns[0] . '_seq'; if (! $fullSchema->hasSequence($checkSequence)) { continue; } $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); } return $visitor->getQueries(); } /** * {@inheritdoc} */ public function getDropAllSchema() { $sm = $this->conn->getSchemaManager(); $visitor = new DropSchemaSqlCollector($this->platform); $schema = $sm->createSchema(); $schema->visit($visitor); return $visitor->getQueries(); } /** * {@inheritdoc} */ public function createSchema(Schema $createSchema) { $this->processSql($this->getCreateSchema($createSchema)); } /** * {@inheritdoc} */ public function updateSchema(Schema $toSchema, $noDrops = false) { $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); } /** * {@inheritdoc} */ public function dropSchema(Schema $dropSchema) { $this->processSqlSafely($this->getDropSchema($dropSchema)); } /** * {@inheritdoc} */ public function dropAllSchema() { $this->processSql($this->getDropAllSchema()); } } dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php 0000644 00000001647 15120025741 0017165 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Abstract Visitor with empty methods for easy extension. */ class AbstractVisitor implements Visitor, NamespaceVisitor { public function acceptSchema(Schema $schema) { } /** * {@inheritdoc} */ public function acceptNamespace($namespaceName) { } public function acceptTable(Table $table) { } public function acceptColumn(Table $table, Column $column) { } public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } public function acceptIndex(Table $table, Index $index) { } public function acceptSequence(Sequence $sequence) { } } dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php 0000644 00000004527 15120025741 0020715 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use function array_merge; class CreateSchemaSqlCollector extends AbstractVisitor { /** @var string[] */ private $createNamespaceQueries = []; /** @var string[] */ private $createTableQueries = []; /** @var string[] */ private $createSequenceQueries = []; /** @var string[] */ private $createFkConstraintQueries = []; /** @var AbstractPlatform */ private $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; } /** * {@inheritdoc} */ public function acceptNamespace($namespaceName) { if (! $this->platform->supportsSchemas()) { return; } $this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName); } /** * {@inheritdoc} */ public function acceptTable(Table $table) { $this->createTableQueries = array_merge($this->createTableQueries, $this->platform->getCreateTableSQL($table)); } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (! $this->platform->supportsForeignKeyConstraints()) { return; } $this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable); } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { $this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence); } /** * @return void */ public function resetQueries() { $this->createNamespaceQueries = []; $this->createTableQueries = []; $this->createSequenceQueries = []; $this->createFkConstraintQueries = []; } /** * Gets all queries collected so far. * * @return string[] */ public function getQueries() { return array_merge( $this->createNamespaceQueries, $this->createTableQueries, $this->createSequenceQueries, $this->createFkConstraintQueries ); } } dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php 0000644 00000005016 15120025741 0020410 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use SplObjectStorage; use function assert; use function strlen; /** * Gathers SQL statements that allow to completely drop the current schema. */ class DropSchemaSqlCollector extends AbstractVisitor { /** @var SplObjectStorage */ private $constraints; /** @var SplObjectStorage */ private $sequences; /** @var SplObjectStorage */ private $tables; /** @var AbstractPlatform */ private $platform; public function __construct(AbstractPlatform $platform) { $this->platform = $platform; $this->initializeQueries(); } /** * {@inheritdoc} */ public function acceptTable(Table $table) { $this->tables->attach($table); } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (strlen($fkConstraint->getName()) === 0) { throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); } $this->constraints->attach($fkConstraint, $localTable); } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { $this->sequences->attach($sequence); } /** * @return void */ public function clearQueries() { $this->initializeQueries(); } /** * @return string[] */ public function getQueries() { $sql = []; foreach ($this->constraints as $fkConstraint) { assert($fkConstraint instanceof ForeignKeyConstraint); $localTable = $this->constraints[$fkConstraint]; $sql[] = $this->platform->getDropForeignKeySQL($fkConstraint, $localTable); } foreach ($this->sequences as $sequence) { assert($sequence instanceof Sequence); $sql[] = $this->platform->getDropSequenceSQL($sequence); } foreach ($this->tables as $table) { assert($table instanceof Table); $sql[] = $this->platform->getDropTableSQL($table); } return $sql; } private function initializeQueries(): void { $this->constraints = new SplObjectStorage(); $this->sequences = new SplObjectStorage(); $this->tables = new SplObjectStorage(); } } dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php 0000644 00000010670 15120025741 0015630 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use function current; use function file_put_contents; use function in_array; use function strtolower; /** * Create a Graphviz output of a Schema. */ class Graphviz extends AbstractVisitor { /** @var string */ private $output = ''; /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { $this->output .= $this->createNodeRelation( $fkConstraint->getLocalTableName() . ':col' . current($fkConstraint->getLocalColumns()) . ':se', $fkConstraint->getForeignTableName() . ':col' . current($fkConstraint->getForeignColumns()) . ':se', [ 'dir' => 'back', 'arrowtail' => 'dot', 'arrowhead' => 'normal', ] ); } /** * {@inheritdoc} */ public function acceptSchema(Schema $schema) { $this->output = 'digraph "' . $schema->getName() . '" {' . "\n"; $this->output .= 'splines = true;' . "\n"; $this->output .= 'overlap = false;' . "\n"; $this->output .= 'outputorder=edgesfirst;' . "\n"; $this->output .= 'mindist = 0.6;' . "\n"; $this->output .= 'sep = .2;' . "\n"; } /** * {@inheritdoc} */ public function acceptTable(Table $table) { $this->output .= $this->createNode( $table->getName(), [ 'label' => $this->createTableLabel($table), 'shape' => 'plaintext', ] ); } /** * @return string */ private function createTableLabel(Table $table) { // Start the table $label = '<<TABLE CELLSPACING="0" BORDER="1" ALIGN="LEFT">'; // The title $label .= '<TR><TD BORDER="1" COLSPAN="3" ALIGN="CENTER" BGCOLOR="#fcaf3e">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $table->getName() . '</FONT></TD></TR>'; // The attributes block foreach ($table->getColumns() as $column) { $columnLabel = $column->getName(); $label .= '<TR>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $columnLabel . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">' . '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="10">' . strtolower($column->getType()) . '</FONT>' . '</TD>' . '<TD BORDER="0" ALIGN="RIGHT" BGCOLOR="#eeeeec" PORT="col' . $column->getName() . '">'; $primaryKey = $table->getPrimaryKey(); if ($primaryKey !== null && in_array($column->getName(), $primaryKey->getColumns())) { $label .= "\xe2\x9c\xb7"; } $label .= '</TD></TR>'; } // End the table $label .= '</TABLE>>'; return $label; } /** * @param string $name * @param string[] $options * * @return string */ private function createNode($name, $options) { $node = $name . ' ['; foreach ($options as $key => $value) { $node .= $key . '=' . $value . ' '; } $node .= "]\n"; return $node; } /** * @param string $node1 * @param string $node2 * @param string[] $options * * @return string */ private function createNodeRelation($node1, $node2, $options) { $relation = $node1 . ' -> ' . $node2 . ' ['; foreach ($options as $key => $value) { $relation .= $key . '=' . $value . ' '; } $relation .= "]\n"; return $relation; } /** * Get Graphviz Output * * @return string */ public function getOutput() { return $this->output . '}'; } /** * Writes dot language output to a file. This should usually be a *.dot file. * * You have to convert the output into a viewable format. For example use "neato" on linux systems * and execute: * * neato -Tpng -o er.png er.dot * * @param string $filename * * @return void */ public function write($filename) { file_put_contents($filename, $this->getOutput()); } } dbal/lib/Doctrine/DBAL/Schema/Visitor/NamespaceVisitor.php 0000644 00000000534 15120025741 0017310 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; /** * Visitor that can visit schema namespaces. */ interface NamespaceVisitor { /** * Accepts a schema namespace name. * * @param string $namespaceName The schema namespace name to accept. * * @return void */ public function acceptNamespace($namespaceName); } dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php 0000644 00000004475 15120025741 0020445 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Removes assets from a schema that are not in the default namespace. * * Some databases such as MySQL support cross databases joins, but don't * allow to call DDLs to a database from another connected database. * Before a schema is serialized into SQL this visitor can cleanup schemas with * non default namespaces. * * This visitor filters all these non-default namespaced tables and sequences * and removes them from the SChema instance. */ class RemoveNamespacedAssets extends AbstractVisitor { /** @var Schema|null */ private $schema; /** * {@inheritdoc} */ public function acceptSchema(Schema $schema) { $this->schema = $schema; } /** * {@inheritdoc} */ public function acceptTable(Table $table) { if ($this->schema === null) { return; } if ($table->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropTable($table->getName()); } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { if ($this->schema === null) { return; } if ($sequence->isInDefaultNamespace($this->schema->getName())) { return; } $this->schema->dropSequence($sequence->getName()); } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if ($this->schema === null) { return; } // The table may already be deleted in a previous // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that // point to nowhere. if (! $this->schema->hasTable($fkConstraint->getForeignTableName())) { $localTable->removeForeignKey($fkConstraint->getName()); return; } $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); if ($foreignTable->isInDefaultNamespace($this->schema->getName())) { return; } $localTable->removeForeignKey($fkConstraint->getName()); } } dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php 0000644 00000002242 15120025741 0017403 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; /** * Visit a SchemaDiff. */ interface SchemaDiffVisitor { /** * Visit an orphaned foreign key whose table was deleted. * * @return void */ public function visitOrphanedForeignKey(ForeignKeyConstraint $foreignKey); /** * Visit a sequence that has changed. * * @return void */ public function visitChangedSequence(Sequence $sequence); /** * Visit a sequence that has been removed. * * @return void */ public function visitRemovedSequence(Sequence $sequence); /** @return void */ public function visitNewSequence(Sequence $sequence); /** @return void */ public function visitNewTable(Table $table); /** @return void */ public function visitNewTableForeignKey(Table $table, ForeignKeyConstraint $foreignKey); /** @return void */ public function visitRemovedTable(Table $table); /** @return void */ public function visitChangedTable(TableDiff $tableDiff); } dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php 0000644 00000001661 15120025742 0015476 0 ustar 00 <?php namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; /** * Schema Visitor used for Validation or Generation purposes. */ interface Visitor { /** * @return void */ public function acceptSchema(Schema $schema); /** * @return void */ public function acceptTable(Table $table); /** * @return void */ public function acceptColumn(Table $table, Column $column); /** * @return void */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); /** * @return void */ public function acceptIndex(Table $table, Index $index); /** * @return void */ public function acceptSequence(Sequence $sequence); } dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php 0000644 00000013106 15120025742 0015140 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_map; use function crc32; use function dechex; use function explode; use function implode; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function substr; /** * The abstract asset allows to reset the name of all assets without publishing this to the public userland. * * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure */ abstract class AbstractAsset { /** @var string */ protected $_name = ''; /** * Namespace of the asset. If none isset the default namespace is assumed. * * @var string|null */ protected $_namespace; /** @var bool */ protected $_quoted = false; /** * Sets the name of this asset. * * @param string $name * * @return void */ protected function _setName($name) { if ($this->isIdentifierQuoted($name)) { $this->_quoted = true; $name = $this->trimQuotes($name); } if (strpos($name, '.') !== false) { $parts = explode('.', $name); $this->_namespace = $parts[0]; $name = $parts[1]; } $this->_name = $name; } /** * Is this asset in the default namespace? * * @param string $defaultNamespaceName * * @return bool */ public function isInDefaultNamespace($defaultNamespaceName) { return $this->_namespace === $defaultNamespaceName || $this->_namespace === null; } /** * Gets the namespace name of this asset. * * If NULL is returned this means the default namespace is used. * * @return string|null */ public function getNamespaceName() { return $this->_namespace; } /** * The shortest name is stripped of the default namespace. All other * namespaced elements are returned as full-qualified names. * * @param string|null $defaultNamespaceName * * @return string */ public function getShortestName($defaultNamespaceName) { $shortestName = $this->getName(); if ($this->_namespace === $defaultNamespaceName) { $shortestName = $this->_name; } return strtolower($shortestName); } /** * The normalized name is full-qualified and lower-cased. Lower-casing is * actually wrong, but we have to do it to keep our sanity. If you are * using database objects that only differentiate in the casing (FOO vs * Foo) then you will NOT be able to use Doctrine Schema abstraction. * * Every non-namespaced element is prefixed with the default namespace * name which is passed as argument to this method. * * @param string $defaultNamespaceName * * @return string */ public function getFullQualifiedName($defaultNamespaceName) { $name = $this->getName(); if (! $this->_namespace) { $name = $defaultNamespaceName . '.' . $name; } return strtolower($name); } /** * Checks if this asset's name is quoted. * * @return bool */ public function isQuoted() { return $this->_quoted; } /** * Checks if this identifier is quoted. * * @param string $identifier * * @return bool */ protected function isIdentifierQuoted($identifier) { return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '['); } /** * Trim quotes from the identifier. * * @param string $identifier * * @return string */ protected function trimQuotes($identifier) { return str_replace(['`', '"', '[', ']'], '', $identifier); } /** * Returns the name of this schema asset. * * @return string */ public function getName() { if ($this->_namespace) { return $this->_namespace . '.' . $this->_name; } return $this->_name; } /** * Gets the quoted representation of this asset but only if it was defined with one. Otherwise * return the plain unquoted value as inserted. * * @return string */ public function getQuotedName(AbstractPlatform $platform) { $keywords = $platform->getReservedKeywordsList(); $parts = explode('.', $this->getName()); foreach ($parts as $k => $v) { $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v; } return implode('.', $parts); } /** * Generates an identifier from a list of column names obeying a certain string length. * * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, * however building idents automatically for foreign keys, composite keys or such can easily create * very long names. * * @param string[] $columnNames * @param string $prefix * @param int $maxSize * * @return string */ protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30) { $hash = implode('', array_map(static function ($column) { return dechex(crc32($column)); }, $columnNames)); return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize)); } } dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php 0000644 00000072735 15120025742 0016571 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ConnectionException; use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use Throwable; use function array_filter; use function array_intersect; use function array_map; use function array_values; use function assert; use function call_user_func_array; use function count; use function func_get_args; use function is_callable; use function is_string; use function preg_match; use function str_replace; use function strtolower; /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. */ abstract class AbstractSchemaManager { /** * Holds instance of the Doctrine connection for this schema manager. * * @var Connection */ protected $_conn; /** * Holds instance of the database platform used for this schema manager. * * @var AbstractPlatform */ protected $_platform; /** * Constructor. Accepts the Connection instance to manage the schema for. */ public function __construct(Connection $conn, ?AbstractPlatform $platform = null) { $this->_conn = $conn; $this->_platform = $platform ?: $this->_conn->getDatabasePlatform(); } /** * Returns the associated platform. * * @return AbstractPlatform */ public function getDatabasePlatform() { return $this->_platform; } /** * Tries any method on the schema manager. Normally a method throws an * exception when your DBMS doesn't support it or if an error occurs. * This method allows you to try and method on your SchemaManager * instance and will return false if it does not work or is not supported. * * <code> * $result = $sm->tryMethod('dropView', 'view_name'); * </code> * * @return mixed */ public function tryMethod() { $args = func_get_args(); $method = $args[0]; unset($args[0]); $args = array_values($args); $callback = [$this, $method]; assert(is_callable($callback)); try { return call_user_func_array($callback, $args); } catch (Throwable $e) { return false; } } /** * Lists the available databases for this connection. * * @return string[] */ public function listDatabases() { $sql = $this->_platform->getListDatabasesSQL(); $databases = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableDatabasesList($databases); } /** * Returns a list of all namespaces in the current database. * * @return string[] */ public function listNamespaceNames() { $sql = $this->_platform->getListNamespacesSQL(); $namespaces = $this->_conn->fetchAllAssociative($sql); return $this->getPortableNamespacesList($namespaces); } /** * Lists the available sequences for this connection. * * @param string|null $database * * @return Sequence[] */ public function listSequences($database = null) { if ($database === null) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListSequencesSQL($database); $sequences = $this->_conn->fetchAllAssociative($sql); return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); } /** * Lists the columns for a given table. * * In contrast to other libraries and to the old version of Doctrine, * this column definition does try to contain the 'primary' column for * the reason that it is not portable across different RDBMS. Use * {@see listTableIndexes($tableName)} to retrieve the primary key * of a table. Where a RDBMS specifies more details, these are held * in the platformDetails array. * * @param string $table The name of the table. * @param string|null $database * * @return Column[] */ public function listTableColumns($table, $database = null) { if (! $database) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListTableColumnsSQL($table, $database); $tableColumns = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableColumnList($table, $database, $tableColumns); } /** * Lists the indexes for a given table returning an array of Index instances. * * Keys of the portable indexes list are all lower-cased. * * @param string $table The name of the table. * * @return Index[] */ public function listTableIndexes($table) { $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); $tableIndexes = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableIndexesList($tableIndexes, $table); } /** * Returns true if all the given tables exist. * * The usage of a string $tableNames is deprecated. Pass a one-element array instead. * * @param string|string[] $names * * @return bool */ public function tablesExist($names) { if (is_string($names)) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'The usage of a string $tableNames in AbstractSchemaManager::tablesExist is deprecated. ' . 'Pass a one-element array instead.' ); } $names = array_map('strtolower', (array) $names); return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames()))); } /** * Returns a list of all tables in the current database. * * @return string[] */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $tables = $this->_conn->fetchAllAssociative($sql); $tableNames = $this->_getPortableTablesList($tables); return $this->filterAssetNames($tableNames); } /** * Filters asset names if they are configured to return only a subset of all * the found elements. * * @param mixed[] $assetNames * * @return mixed[] */ protected function filterAssetNames($assetNames) { $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter(); if (! $filter) { return $assetNames; } return array_values(array_filter($assetNames, $filter)); } /** * @deprecated Use Configuration::getSchemaAssetsFilter() instead * * @return string|null */ protected function getFilterSchemaAssetsExpression() { return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression(); } /** * Lists the tables for this connection. * * @return Table[] */ public function listTables() { $tableNames = $this->listTableNames(); $tables = []; foreach ($tableNames as $tableName) { $tables[] = $this->listTableDetails($tableName); } return $tables; } /** * @param string $name * * @return Table */ public function listTableDetails($name) { $columns = $this->listTableColumns($name); $foreignKeys = []; if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($name); } $indexes = $this->listTableIndexes($name); return new Table($name, $columns, $indexes, $foreignKeys); } /** * Lists the views this connection has. * * @return View[] */ public function listViews() { $database = $this->_conn->getDatabase(); $sql = $this->_platform->getListViewsSQL($database); $views = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableViewsList($views); } /** * Lists the foreign keys for the given table. * * @param string $table The name of the table. * @param string|null $database * * @return ForeignKeyConstraint[] */ public function listTableForeignKeys($table, $database = null) { if ($database === null) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); $tableForeignKeys = $this->_conn->fetchAllAssociative($sql); return $this->_getPortableTableForeignKeysList($tableForeignKeys); } /* drop*() Methods */ /** * Drops a database. * * NOTE: You can not drop the database this SchemaManager is currently connected to. * * @param string $database The name of the database to drop. * * @return void */ public function dropDatabase($database) { $this->_execSql($this->_platform->getDropDatabaseSQL($database)); } /** * Drops the given table. * * @param string $name The name of the table to drop. * * @return void */ public function dropTable($name) { $this->_execSql($this->_platform->getDropTableSQL($name)); } /** * Drops the index from the given table. * * @param Index|string $index The name of the index. * @param Table|string $table The name of the table. * * @return void */ public function dropIndex($index, $table) { if ($index instanceof Index) { $index = $index->getQuotedName($this->_platform); } $this->_execSql($this->_platform->getDropIndexSQL($index, $table)); } /** * Drops the constraint from the given table. * * @param Table|string $table The name of the table. * * @return void */ public function dropConstraint(Constraint $constraint, $table) { $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table)); } /** * Drops a foreign key from a table. * * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key. * @param Table|string $table The name of the table with the foreign key. * * @return void */ public function dropForeignKey($foreignKey, $table) { $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table)); } /** * Drops a sequence with a given name. * * @param string $name The name of the sequence to drop. * * @return void */ public function dropSequence($name) { $this->_execSql($this->_platform->getDropSequenceSQL($name)); } /** * Drops a view. * * @param string $name The name of the view. * * @return void */ public function dropView($name) { $this->_execSql($this->_platform->getDropViewSQL($name)); } /* create*() Methods */ /** * Creates a new database. * * @param string $database The name of the database to create. * * @return void */ public function createDatabase($database) { $this->_execSql($this->_platform->getCreateDatabaseSQL($database)); } /** * Creates a new table. * * @return void */ public function createTable(Table $table) { $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS; $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); } /** * Creates a new sequence. * * @param Sequence $sequence * * @return void * * @throws ConnectionException If something fails at database level. */ public function createSequence($sequence) { $this->_execSql($this->_platform->getCreateSequenceSQL($sequence)); } /** * Creates a constraint on a table. * * @param Table|string $table * * @return void */ public function createConstraint(Constraint $constraint, $table) { $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table)); } /** * Creates a new index on a table. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void */ public function createIndex(Index $index, $table) { $this->_execSql($this->_platform->getCreateIndexSQL($index, $table)); } /** * Creates a new foreign key. * * @param ForeignKeyConstraint $foreignKey The ForeignKey instance. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table)); } /** * Creates a new view. * * @return void */ public function createView(View $view) { $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql())); } /* dropAndCreate*() Methods */ /** * Drops and creates a constraint. * * @see dropConstraint() * @see createConstraint() * * @param Table|string $table * * @return void */ public function dropAndCreateConstraint(Constraint $constraint, $table) { $this->tryMethod('dropConstraint', $constraint, $table); $this->createConstraint($constraint, $table); } /** * Drops and creates a new index on a table. * * @param Table|string $table The name of the table on which the index is to be created. * * @return void */ public function dropAndCreateIndex(Index $index, $table) { $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); $this->createIndex($index, $table); } /** * Drops and creates a new foreign key. * * @param ForeignKeyConstraint $foreignKey An associative array that defines properties * of the foreign key to be created. * @param Table|string $table The name of the table on which the foreign key is to be created. * * @return void */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->tryMethod('dropForeignKey', $foreignKey, $table); $this->createForeignKey($foreignKey, $table); } /** * Drops and create a new sequence. * * @return void * * @throws ConnectionException If something fails at database level. */ public function dropAndCreateSequence(Sequence $sequence) { $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); $this->createSequence($sequence); } /** * Drops and creates a new table. * * @return void */ public function dropAndCreateTable(Table $table) { $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); $this->createTable($table); } /** * Drops and creates a new database. * * @param string $database The name of the database to create. * * @return void */ public function dropAndCreateDatabase($database) { $this->tryMethod('dropDatabase', $database); $this->createDatabase($database); } /** * Drops and creates a new view. * * @return void */ public function dropAndCreateView(View $view) { $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); $this->createView($view); } /* alterTable() Methods */ /** * Alters an existing tables schema. * * @return void */ public function alterTable(TableDiff $tableDiff) { foreach ($this->_platform->getAlterTableSQL($tableDiff) as $ddlQuery) { $this->_execSql($ddlQuery); } } /** * Renames a given table to another name. * * @param string $name The current name of the table. * @param string $newName The new name of the table. * * @return void */ public function renameTable($name, $newName) { $tableDiff = new TableDiff($name); $tableDiff->newName = $newName; $this->alterTable($tableDiff); } /** * Methods for filtering return values of list*() methods to convert * the native DBMS data definition to a portable Doctrine definition */ /** * @param mixed[] $databases * * @return string[] */ protected function _getPortableDatabasesList($databases) { $list = []; foreach ($databases as $value) { $value = $this->_getPortableDatabaseDefinition($value); if (! $value) { continue; } $list[] = $value; } return $list; } /** * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition. * * @param mixed[][] $namespaces The list of namespace names in the native DBMS data definition. * * @return string[] */ protected function getPortableNamespacesList(array $namespaces) { $namespacesList = []; foreach ($namespaces as $namespace) { $namespacesList[] = $this->getPortableNamespaceDefinition($namespace); } return $namespacesList; } /** * @param mixed $database * * @return mixed */ protected function _getPortableDatabaseDefinition($database) { return $database; } /** * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition. * * @param mixed[] $namespace The native DBMS namespace definition. * * @return mixed */ protected function getPortableNamespaceDefinition(array $namespace) { return $namespace; } /** * @deprecated * * @param mixed[][] $functions * * @return mixed[][] */ protected function _getPortableFunctionsList($functions) { $list = []; foreach ($functions as $value) { $value = $this->_getPortableFunctionDefinition($value); if (! $value) { continue; } $list[] = $value; } return $list; } /** * @deprecated * * @param mixed[] $function * * @return mixed */ protected function _getPortableFunctionDefinition($function) { return $function; } /** * @param mixed[][] $triggers * * @return mixed[][] */ protected function _getPortableTriggersList($triggers) { $list = []; foreach ($triggers as $value) { $value = $this->_getPortableTriggerDefinition($value); if (! $value) { continue; } $list[] = $value; } return $list; } /** * @param mixed[] $trigger * * @return mixed */ protected function _getPortableTriggerDefinition($trigger) { return $trigger; } /** * @param mixed[][] $sequences * * @return Sequence[] */ protected function _getPortableSequencesList($sequences) { $list = []; foreach ($sequences as $value) { $list[] = $this->_getPortableSequenceDefinition($value); } return $list; } /** * @param mixed[] $sequence * * @return Sequence * * @throws Exception */ protected function _getPortableSequenceDefinition($sequence) { throw Exception::notSupported('Sequences'); } /** * Independent of the database the keys of the column list result are lowercased. * * The name of the created column instance however is kept in its case. * * @param string $table The name of the table. * @param string $database * @param mixed[][] $tableColumns * * @return Column[] */ protected function _getPortableTableColumnList($table, $database, $tableColumns) { $eventManager = $this->_platform->getEventManager(); $list = []; foreach ($tableColumns as $tableColumn) { $column = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $column = $eventArgs->getColumn(); } if (! $defaultPrevented) { $column = $this->_getPortableTableColumnDefinition($tableColumn); } if (! $column) { continue; } $name = strtolower($column->getQuotedName($this->_platform)); $list[$name] = $column; } return $list; } /** * Gets Table Column Definition. * * @param mixed[] $tableColumn * * @return Column */ abstract protected function _getPortableTableColumnDefinition($tableColumn); /** * Aggregates and groups the index results according to the required data result. * * @param mixed[][] $tableIndexes * @param string|null $tableName * * @return Index[] */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $result = []; foreach ($tableIndexes as $tableIndex) { $indexName = $keyName = $tableIndex['key_name']; if ($tableIndex['primary']) { $keyName = 'primary'; } $keyName = strtolower($keyName); if (! isset($result[$keyName])) { $options = [ 'lengths' => [], ]; if (isset($tableIndex['where'])) { $options['where'] = $tableIndex['where']; } $result[$keyName] = [ 'name' => $indexName, 'columns' => [], 'unique' => ! $tableIndex['non_unique'], 'primary' => $tableIndex['primary'], 'flags' => $tableIndex['flags'] ?? [], 'options' => $options, ]; } $result[$keyName]['columns'][] = $tableIndex['column_name']; $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null; } $eventManager = $this->_platform->getEventManager(); $indexes = []; foreach ($result as $indexKey => $data) { $index = null; $defaultPrevented = false; if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); $defaultPrevented = $eventArgs->isDefaultPrevented(); $index = $eventArgs->getIndex(); } if (! $defaultPrevented) { $index = new Index( $data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options'] ); } if (! $index) { continue; } $indexes[$indexKey] = $index; } return $indexes; } /** * @param mixed[][] $tables * * @return string[] */ protected function _getPortableTablesList($tables) { $list = []; foreach ($tables as $value) { $value = $this->_getPortableTableDefinition($value); if (! $value) { continue; } $list[] = $value; } return $list; } /** * @param mixed $table * * @return string */ protected function _getPortableTableDefinition($table) { return $table; } /** * @param mixed[][] $users * * @return string[][] */ protected function _getPortableUsersList($users) { $list = []; foreach ($users as $value) { $value = $this->_getPortableUserDefinition($value); if (! $value) { continue; } $list[] = $value; } return $list; } /** * @param string[] $user * * @return string[] */ protected function _getPortableUserDefinition($user) { return $user; } /** * @param mixed[][] $views * * @return View[] */ protected function _getPortableViewsList($views) { $list = []; foreach ($views as $value) { $view = $this->_getPortableViewDefinition($value); if (! $view) { continue; } $viewName = strtolower($view->getQuotedName($this->_platform)); $list[$viewName] = $view; } return $list; } /** * @param mixed[] $view * * @return View|false */ protected function _getPortableViewDefinition($view) { return false; } /** * @param mixed[][] $tableForeignKeys * * @return ForeignKeyConstraint[] */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $list[] = $this->_getPortableTableForeignKeyDefinition($value); } return $list; } /** * @param mixed $tableForeignKey * * @return ForeignKeyConstraint */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return $tableForeignKey; } /** * @param string[]|string $sql * * @return void */ protected function _execSql($sql) { foreach ((array) $sql as $query) { $this->_conn->executeStatement($query); } } /** * Creates a schema instance for the current database. * * @return Schema */ public function createSchema() { $namespaces = []; if ($this->_platform->supportsSchemas()) { $namespaces = $this->listNamespaceNames(); } $sequences = []; if ($this->_platform->supportsSequences()) { $sequences = $this->listSequences(); } $tables = $this->listTables(); return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces); } /** * Creates the configuration for this schema. * * @return SchemaConfig */ public function createSchemaConfig() { $schemaConfig = new SchemaConfig(); $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); $searchPaths = $this->getSchemaSearchPaths(); if (isset($searchPaths[0])) { $schemaConfig->setName($searchPaths[0]); } $params = $this->_conn->getParams(); if (! isset($params['defaultTableOptions'])) { $params['defaultTableOptions'] = []; } if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) { $params['defaultTableOptions']['charset'] = $params['charset']; } $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); return $schemaConfig; } /** * The search path for namespaces in the currently connected database. * * The first entry is usually the default namespace in the Schema. All * further namespaces contain tables/sequences which can also be addressed * with a short, not full-qualified name. * * For databases that don't support subschema/namespaces this method * returns the name of the currently connected database. * * @return string[] */ public function getSchemaSearchPaths() { return [$this->_conn->getDatabase()]; } /** * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns * the type given as default. * * @param string|null $comment * @param string $currentType * * @return string */ public function extractDoctrineTypeFromComment($comment, $currentType) { if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match)) { return $match[1]; } return $currentType; } /** * @param string|null $comment * @param string|null $type * * @return string|null */ public function removeDoctrineTypeFromComment($comment, $type) { if ($comment === null) { return null; } return str_replace('(DC2Type:' . $type . ')', '', $comment); } } dbal/lib/Doctrine/DBAL/Schema/Column.php 0000644 00000020445 15120025742 0013636 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_merge; use function is_numeric; use function method_exists; /** * Object representation of a database column. */ class Column extends AbstractAsset { /** @var Type */ protected $_type; /** @var int|null */ protected $_length; /** @var int */ protected $_precision = 10; /** @var int */ protected $_scale = 0; /** @var bool */ protected $_unsigned = false; /** @var bool */ protected $_fixed = false; /** @var bool */ protected $_notnull = true; /** @var string|null */ protected $_default; /** @var bool */ protected $_autoincrement = false; /** @var mixed[] */ protected $_platformOptions = []; /** @var string|null */ protected $_columnDefinition; /** @var string|null */ protected $_comment; /** @var mixed[] */ protected $_customSchemaOptions = []; /** * Creates a new Column. * * @param string $name * @param mixed[] $options */ public function __construct($name, Type $type, array $options = []) { $this->_setName($name); $this->setType($type); $this->setOptions($options); } /** * @param mixed[] $options * * @return Column */ public function setOptions(array $options) { foreach ($options as $name => $value) { $method = 'set' . $name; if (! method_exists($this, $method)) { // next major: throw an exception Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/2846', 'The "%s" column option is not supported,' . ' setting unknown options is deprecated and will cause an error in Doctrine DBAL 3.0', $name ); continue; } $this->$method($value); } return $this; } /** * @return Column */ public function setType(Type $type) { $this->_type = $type; return $this; } /** * @param int|null $length * * @return Column */ public function setLength($length) { if ($length !== null) { $this->_length = (int) $length; } else { $this->_length = null; } return $this; } /** * @param int $precision * * @return Column */ public function setPrecision($precision) { if (! is_numeric($precision)) { $precision = 10; // defaults to 10 when no valid precision is given. } $this->_precision = (int) $precision; return $this; } /** * @param int $scale * * @return Column */ public function setScale($scale) { if (! is_numeric($scale)) { $scale = 0; } $this->_scale = (int) $scale; return $this; } /** * @param bool $unsigned * * @return Column */ public function setUnsigned($unsigned) { $this->_unsigned = (bool) $unsigned; return $this; } /** * @param bool $fixed * * @return Column */ public function setFixed($fixed) { $this->_fixed = (bool) $fixed; return $this; } /** * @param bool $notnull * * @return Column */ public function setNotnull($notnull) { $this->_notnull = (bool) $notnull; return $this; } /** * @param mixed $default * * @return Column */ public function setDefault($default) { $this->_default = $default; return $this; } /** * @param mixed[] $platformOptions * * @return Column */ public function setPlatformOptions(array $platformOptions) { $this->_platformOptions = $platformOptions; return $this; } /** * @param string $name * @param mixed $value * * @return Column */ public function setPlatformOption($name, $value) { $this->_platformOptions[$name] = $value; return $this; } /** * @param string $value * * @return Column */ public function setColumnDefinition($value) { $this->_columnDefinition = $value; return $this; } /** * @return Type */ public function getType() { return $this->_type; } /** * @return int|null */ public function getLength() { return $this->_length; } /** * @return int */ public function getPrecision() { return $this->_precision; } /** * @return int */ public function getScale() { return $this->_scale; } /** * @return bool */ public function getUnsigned() { return $this->_unsigned; } /** * @return bool */ public function getFixed() { return $this->_fixed; } /** * @return bool */ public function getNotnull() { return $this->_notnull; } /** * @return string|null */ public function getDefault() { return $this->_default; } /** * @return mixed[] */ public function getPlatformOptions() { return $this->_platformOptions; } /** * @param string $name * * @return bool */ public function hasPlatformOption($name) { return isset($this->_platformOptions[$name]); } /** * @param string $name * * @return mixed */ public function getPlatformOption($name) { return $this->_platformOptions[$name]; } /** * @return string|null */ public function getColumnDefinition() { return $this->_columnDefinition; } /** * @return bool */ public function getAutoincrement() { return $this->_autoincrement; } /** * @param bool $flag * * @return Column */ public function setAutoincrement($flag) { $this->_autoincrement = $flag; return $this; } /** * @param string|null $comment * * @return Column */ public function setComment($comment) { $this->_comment = $comment; return $this; } /** * @return string|null */ public function getComment() { return $this->_comment; } /** * @param string $name * @param mixed $value * * @return Column */ public function setCustomSchemaOption($name, $value) { $this->_customSchemaOptions[$name] = $value; return $this; } /** * @param string $name * * @return bool */ public function hasCustomSchemaOption($name) { return isset($this->_customSchemaOptions[$name]); } /** * @param string $name * * @return mixed */ public function getCustomSchemaOption($name) { return $this->_customSchemaOptions[$name]; } /** * @param mixed[] $customSchemaOptions * * @return Column */ public function setCustomSchemaOptions(array $customSchemaOptions) { $this->_customSchemaOptions = $customSchemaOptions; return $this; } /** * @return mixed[] */ public function getCustomSchemaOptions() { return $this->_customSchemaOptions; } /** * @return mixed[] */ public function toArray() { return array_merge([ 'name' => $this->_name, 'type' => $this->_type, 'default' => $this->_default, 'notnull' => $this->_notnull, 'length' => $this->_length, 'precision' => $this->_precision, 'scale' => $this->_scale, 'fixed' => $this->_fixed, 'unsigned' => $this->_unsigned, 'autoincrement' => $this->_autoincrement, 'columnDefinition' => $this->_columnDefinition, 'comment' => $this->_comment, ], $this->_platformOptions, $this->_customSchemaOptions); } } dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php 0000644 00000002320 15120025742 0014417 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use function in_array; /** * Represents the change of a column. */ class ColumnDiff { /** @var string */ public $oldColumnName; /** @var Column */ public $column; /** @var string[] */ public $changedProperties = []; /** @var Column|null */ public $fromColumn; /** * @param string $oldColumnName * @param string[] $changedProperties */ public function __construct( $oldColumnName, Column $column, array $changedProperties = [], ?Column $fromColumn = null ) { $this->oldColumnName = $oldColumnName; $this->column = $column; $this->changedProperties = $changedProperties; $this->fromColumn = $fromColumn; } /** * @param string $propertyName * * @return bool */ public function hasChanged($propertyName) { return in_array($propertyName, $this->changedProperties); } /** * @return Identifier */ public function getOldColumnName() { $quote = $this->fromColumn && $this->fromColumn->isQuoted(); return new Identifier($this->oldColumnName, $quote); } } dbal/lib/Doctrine/DBAL/Schema/Comparator.php 0000644 00000046721 15120025742 0014515 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Types; use function array_intersect_key; use function array_key_exists; use function array_keys; use function array_map; use function array_merge; use function array_shift; use function array_unique; use function assert; use function count; use function get_class; use function strtolower; /** * Compares two Schemas and return an instance of SchemaDiff. */ class Comparator { /** * @return SchemaDiff */ public static function compareSchemas(Schema $fromSchema, Schema $toSchema) { $c = new self(); return $c->compare($fromSchema, $toSchema); } /** * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. * * The returned differences are returned in such a way that they contain the * operations to change the schema stored in $fromSchema to the schema that is * stored in $toSchema. * * @return SchemaDiff */ public function compare(Schema $fromSchema, Schema $toSchema) { $diff = new SchemaDiff(); $diff->fromSchema = $fromSchema; $foreignKeysToTable = []; foreach ($toSchema->getNamespaces() as $namespace) { if ($fromSchema->hasNamespace($namespace)) { continue; } $diff->newNamespaces[$namespace] = $namespace; } foreach ($fromSchema->getNamespaces() as $namespace) { if ($toSchema->hasNamespace($namespace)) { continue; } $diff->removedNamespaces[$namespace] = $namespace; } foreach ($toSchema->getTables() as $table) { $tableName = $table->getShortestName($toSchema->getName()); if (! $fromSchema->hasTable($tableName)) { $diff->newTables[$tableName] = $toSchema->getTable($tableName); } else { $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $toSchema->getTable($tableName) ); if ($tableDifferences !== false) { $diff->changedTables[$tableName] = $tableDifferences; } } } /* Check if there are tables removed */ foreach ($fromSchema->getTables() as $table) { $tableName = $table->getShortestName($fromSchema->getName()); $table = $fromSchema->getTable($tableName); if (! $toSchema->hasTable($tableName)) { $diff->removedTables[$tableName] = $table; } // also remember all foreign keys that point to a specific table foreach ($table->getForeignKeys() as $foreignKey) { $foreignTable = strtolower($foreignKey->getForeignTableName()); if (! isset($foreignKeysToTable[$foreignTable])) { $foreignKeysToTable[$foreignTable] = []; } $foreignKeysToTable[$foreignTable][] = $foreignKey; } } foreach ($diff->removedTables as $tableName => $table) { if (! isset($foreignKeysToTable[$tableName])) { continue; } $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]); // deleting duplicated foreign keys present on both on the orphanedForeignKey // and the removedForeignKeys from changedTables foreach ($foreignKeysToTable[$tableName] as $foreignKey) { // strtolower the table name to make if compatible with getShortestName $localTableName = strtolower($foreignKey->getLocalTableName()); if (! isset($diff->changedTables[$localTableName])) { continue; } foreach ($diff->changedTables[$localTableName]->removedForeignKeys as $key => $removedForeignKey) { assert($removedForeignKey instanceof ForeignKeyConstraint); // We check if the key is from the removed table if not we skip. if ($tableName !== strtolower($removedForeignKey->getForeignTableName())) { continue; } unset($diff->changedTables[$localTableName]->removedForeignKeys[$key]); } } } foreach ($toSchema->getSequences() as $sequence) { $sequenceName = $sequence->getShortestName($toSchema->getName()); if (! $fromSchema->hasSequence($sequenceName)) { if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { $diff->newSequences[] = $sequence; } } else { if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { $diff->changedSequences[] = $toSchema->getSequence($sequenceName); } } } foreach ($fromSchema->getSequences() as $sequence) { if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { continue; } $sequenceName = $sequence->getShortestName($fromSchema->getName()); if ($toSchema->hasSequence($sequenceName)) { continue; } $diff->removedSequences[] = $sequence; } return $diff; } /** * @param Schema $schema * @param Sequence $sequence * * @return bool */ private function isAutoIncrementSequenceInSchema($schema, $sequence) { foreach ($schema->getTables() as $table) { if ($sequence->isAutoIncrementsFor($table)) { return true; } } return false; } /** * @return bool */ public function diffSequence(Sequence $sequence1, Sequence $sequence2) { if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) { return true; } return $sequence1->getInitialValue() !== $sequence2->getInitialValue(); } /** * Returns the difference between the tables $fromTable and $toTable. * * If there are no differences this method returns the boolean false. * * @return TableDiff|false */ public function diffTable(Table $fromTable, Table $toTable) { $changes = 0; $tableDifferences = new TableDiff($fromTable->getName()); $tableDifferences->fromTable = $fromTable; $fromTableColumns = $fromTable->getColumns(); $toTableColumns = $toTable->getColumns(); /* See if all the columns in "from" table exist in "to" table */ foreach ($toTableColumns as $columnName => $column) { if ($fromTable->hasColumn($columnName)) { continue; } $tableDifferences->addedColumns[$columnName] = $column; $changes++; } /* See if there are any removed columns in "to" table */ foreach ($fromTableColumns as $columnName => $column) { // See if column is removed in "to" table. if (! $toTable->hasColumn($columnName)) { $tableDifferences->removedColumns[$columnName] = $column; $changes++; continue; } // See if column has changed properties in "to" table. $changedProperties = $this->diffColumn($column, $toTable->getColumn($columnName)); if (empty($changedProperties)) { continue; } $columnDiff = new ColumnDiff($column->getName(), $toTable->getColumn($columnName), $changedProperties); $columnDiff->fromColumn = $column; $tableDifferences->changedColumns[$column->getName()] = $columnDiff; $changes++; } $this->detectColumnRenamings($tableDifferences); $fromTableIndexes = $fromTable->getIndexes(); $toTableIndexes = $toTable->getIndexes(); /* See if all the indexes in "from" table exist in "to" table */ foreach ($toTableIndexes as $indexName => $index) { if (($index->isPrimary() && $fromTable->hasPrimaryKey()) || $fromTable->hasIndex($indexName)) { continue; } $tableDifferences->addedIndexes[$indexName] = $index; $changes++; } /* See if there are any removed indexes in "to" table */ foreach ($fromTableIndexes as $indexName => $index) { // See if index is removed in "to" table. if ( ($index->isPrimary() && ! $toTable->hasPrimaryKey()) || ! $index->isPrimary() && ! $toTable->hasIndex($indexName) ) { $tableDifferences->removedIndexes[$indexName] = $index; $changes++; continue; } // See if index has changed in "to" table. $toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName); assert($toTableIndex instanceof Index); if (! $this->diffIndex($index, $toTableIndex)) { continue; } $tableDifferences->changedIndexes[$indexName] = $toTableIndex; $changes++; } $this->detectIndexRenamings($tableDifferences); $fromForeignKeys = $fromTable->getForeignKeys(); $toForeignKeys = $toTable->getForeignKeys(); foreach ($fromForeignKeys as $fromKey => $fromConstraint) { foreach ($toForeignKeys as $toKey => $toConstraint) { if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) { unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } else { if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) { $tableDifferences->changedForeignKeys[] = $toConstraint; $changes++; unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]); } } } } foreach ($fromForeignKeys as $fromConstraint) { $tableDifferences->removedForeignKeys[] = $fromConstraint; $changes++; } foreach ($toForeignKeys as $toConstraint) { $tableDifferences->addedForeignKeys[] = $toConstraint; $changes++; } return $changes ? $tableDifferences : false; } /** * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @return void */ private function detectColumnRenamings(TableDiff $tableDifferences) { $renameCandidates = []; foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) { foreach ($tableDifferences->removedColumns as $removedColumn) { if (count($this->diffColumn($addedColumn, $removedColumn)) !== 0) { continue; } $renameCandidates[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName]; } } foreach ($renameCandidates as $candidateColumns) { if (count($candidateColumns) !== 1) { continue; } [$removedColumn, $addedColumn] = $candidateColumns[0]; $removedColumnName = strtolower($removedColumn->getName()); $addedColumnName = strtolower($addedColumn->getName()); if (isset($tableDifferences->renamedColumns[$removedColumnName])) { continue; } $tableDifferences->renamedColumns[$removedColumnName] = $addedColumn; unset( $tableDifferences->addedColumns[$addedColumnName], $tableDifferences->removedColumns[$removedColumnName] ); } } /** * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop * however ambiguities between different possibilities should not lead to renaming at all. * * @return void */ private function detectIndexRenamings(TableDiff $tableDifferences) { $renameCandidates = []; // Gather possible rename candidates by comparing each added and removed index based on semantics. foreach ($tableDifferences->addedIndexes as $addedIndexName => $addedIndex) { foreach ($tableDifferences->removedIndexes as $removedIndex) { if ($this->diffIndex($addedIndex, $removedIndex)) { continue; } $renameCandidates[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName]; } } foreach ($renameCandidates as $candidateIndexes) { // If the current rename candidate contains exactly one semantically equal index, // we can safely rename it. // Otherwise it is unclear if a rename action is really intended, // therefore we let those ambiguous indexes be added/dropped. if (count($candidateIndexes) !== 1) { continue; } [$removedIndex, $addedIndex] = $candidateIndexes[0]; $removedIndexName = strtolower($removedIndex->getName()); $addedIndexName = strtolower($addedIndex->getName()); if (isset($tableDifferences->renamedIndexes[$removedIndexName])) { continue; } $tableDifferences->renamedIndexes[$removedIndexName] = $addedIndex; unset( $tableDifferences->addedIndexes[$addedIndexName], $tableDifferences->removedIndexes[$removedIndexName] ); } } /** * @return bool */ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) { if ( array_map('strtolower', $key1->getUnquotedLocalColumns()) !== array_map('strtolower', $key2->getUnquotedLocalColumns()) ) { return true; } if ( array_map('strtolower', $key1->getUnquotedForeignColumns()) !== array_map('strtolower', $key2->getUnquotedForeignColumns()) ) { return true; } if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { return true; } if ($key1->onUpdate() !== $key2->onUpdate()) { return true; } return $key1->onDelete() !== $key2->onDelete(); } /** * Returns the difference between the columns * * If there are differences this method returns $field2, otherwise the * boolean false. * * @return string[] */ public function diffColumn(Column $column1, Column $column2) { $properties1 = $column1->toArray(); $properties2 = $column2->toArray(); $changedProperties = []; if (get_class($properties1['type']) !== get_class($properties2['type'])) { $changedProperties[] = 'type'; } foreach (['notnull', 'unsigned', 'autoincrement'] as $property) { if ($properties1[$property] === $properties2[$property]) { continue; } $changedProperties[] = $property; } // This is a very nasty hack to make comparator work with the legacy json_array type, // which should be killed in v3 if ($this->isALegacyJsonComparison($properties1['type'], $properties2['type'])) { array_shift($changedProperties); $changedProperties[] = 'comment'; } // Null values need to be checked additionally as they tell whether to create or drop a default value. // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. if ( ($properties1['default'] === null) !== ($properties2['default'] === null) || $properties1['default'] != $properties2['default'] ) { $changedProperties[] = 'default'; } if ( ($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) || $properties1['type'] instanceof Types\BinaryType ) { // check if value of length is set at all, default value assumed otherwise. $length1 = $properties1['length'] ?: 255; $length2 = $properties2['length'] ?: 255; if ($length1 !== $length2) { $changedProperties[] = 'length'; } if ($properties1['fixed'] !== $properties2['fixed']) { $changedProperties[] = 'fixed'; } } elseif ($properties1['type'] instanceof Types\DecimalType) { if (($properties1['precision'] ?: 10) !== ($properties2['precision'] ?: 10)) { $changedProperties[] = 'precision'; } if ($properties1['scale'] !== $properties2['scale']) { $changedProperties[] = 'scale'; } } // A null value and an empty string are actually equal for a comment so they should not trigger a change. if ( $properties1['comment'] !== $properties2['comment'] && ! ($properties1['comment'] === null && $properties2['comment'] === '') && ! ($properties2['comment'] === null && $properties1['comment'] === '') ) { $changedProperties[] = 'comment'; } $customOptions1 = $column1->getCustomSchemaOptions(); $customOptions2 = $column2->getCustomSchemaOptions(); foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) { if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) { $changedProperties[] = $key; } elseif ($properties1[$key] !== $properties2[$key]) { $changedProperties[] = $key; } } $platformOptions1 = $column1->getPlatformOptions(); $platformOptions2 = $column2->getPlatformOptions(); foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) { if ($properties1[$key] === $properties2[$key]) { continue; } $changedProperties[] = $key; } return array_unique($changedProperties); } /** * TODO: kill with fire on v3.0 * * @deprecated */ private function isALegacyJsonComparison(Types\Type $one, Types\Type $other): bool { if (! $one instanceof Types\JsonType || ! $other instanceof Types\JsonType) { return false; } return (! $one instanceof Types\JsonArrayType && $other instanceof Types\JsonArrayType) || (! $other instanceof Types\JsonArrayType && $one instanceof Types\JsonArrayType); } /** * Finds the difference between the indexes $index1 and $index2. * * Compares $index1 with $index2 and returns $index2 if there are any * differences or false in case there are no differences. * * @return bool */ public function diffIndex(Index $index1, Index $index2) { return ! ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)); } } dbal/lib/Doctrine/DBAL/Schema/Constraint.php 0000644 00000001741 15120025742 0014523 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Marker interface for constraints. */ interface Constraint { /** * @return string */ public function getName(); /** * @return string */ public function getQuotedName(AbstractPlatform $platform); /** * Returns the names of the referencing table columns * the constraint is associated with. * * @return string[] */ public function getColumns(); /** * Returns the quoted representation of the column names * the constraint is associated with. * * But only if they were defined with one or a column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform); } dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php 0000644 00000015752 15120025742 0015371 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\DB2Platform; use Doctrine\DBAL\Types\Type; use function array_change_key_case; use function assert; use function preg_match; use function str_replace; use function strpos; use function strtolower; use function substr; use const CASE_LOWER; /** * IBM Db2 Schema Manager. */ class DB2SchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} * * Apparently creator is the schema not the user who created it: * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm} */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $sql .= ' AND CREATOR = UPPER(' . $this->_conn->quote($this->_conn->getUsername()) . ')'; $tables = $this->_conn->fetchAllAssociative($sql); return $this->filterAssetNames($this->_getPortableTablesList($tables)); } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $length = null; $fixed = null; $scale = false; $precision = false; $default = null; if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') { $default = $tableColumn['default']; if (preg_match('/^\'(.*)\'$/s', $default, $matches)) { $default = str_replace("''", "'", $matches[1]); } } $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); if (isset($tableColumn['comment'])) { $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); } switch (strtolower($tableColumn['typename'])) { case 'varchar': $length = $tableColumn['length']; $fixed = false; break; case 'character': $length = $tableColumn['length']; $fixed = true; break; case 'clob': $length = $tableColumn['length']; break; case 'decimal': case 'double': case 'real': $scale = $tableColumn['scale']; $precision = $tableColumn['length']; break; } $options = [ 'length' => $length, 'unsigned' => false, 'fixed' => (bool) $fixed, 'default' => $default, 'autoincrement' => (bool) $tableColumn['autoincrement'], 'notnull' => $tableColumn['nulls'] === 'N', 'scale' => null, 'precision' => null, 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, 'platformOptions' => [], ]; if ($scale !== null && $precision !== null) { $options['scale'] = $scale; $options['precision'] = $precision; } return new Column($tableColumn['colname'], Type::getType($type), $options); } /** * {@inheritdoc} */ protected function _getPortableTablesList($tables) { $tableNames = []; foreach ($tables as $tableRow) { $tableRow = array_change_key_case($tableRow, CASE_LOWER); $tableNames[] = $tableRow['name']; } return $tableNames; } /** * {@inheritdoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndexRow) { $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER); $tableIndexRow['primary'] = (bool) $tableIndexRow['primary']; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'] ); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); if (! isset($foreignKeys[$tableForeignKey['index_name']])) { $foreignKeys[$tableForeignKey['index_name']] = [ 'local_columns' => [$tableForeignKey['local_column']], 'foreign_table' => $tableForeignKey['foreign_table'], 'foreign_columns' => [$tableForeignKey['foreign_column']], 'name' => $tableForeignKey['index_name'], 'options' => [ 'onUpdate' => $tableForeignKey['on_update'], 'onDelete' => $tableForeignKey['on_delete'], ], ]; } else { $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * @param string $def * * @return string|null */ protected function _getPortableForeignKeyRuleDef($def) { if ($def === 'C') { return 'CASCADE'; } if ($def === 'N') { return 'SET NULL'; } return null; } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, CASE_LOWER); $sql = ''; $pos = strpos($view['text'], ' AS '); if ($pos !== false) { $sql = substr($view['text'], $pos + 4); } return new View($view['name'], $sql); } /** * {@inheritdoc} */ public function listTableDetails($name): Table { $table = parent::listTableDetails($name); $platform = $this->_platform; assert($platform instanceof DB2Platform); $sql = $platform->getListTableCommentsSQL($name); $tableOptions = $this->_conn->fetchAssociative($sql); if ($tableOptions !== false) { $table->addOption('comment', $tableOptions['REMARKS']); } return $table; } } dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php 0000644 00000006062 15120025742 0016437 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Types\Type; use function explode; use function strtolower; use function trim; /** * Schema manager for the Drizzle RDBMS. */ class DrizzleSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtolower($tableColumn['DATA_TYPE']); $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); $tableColumn['COLUMN_COMMENT'] = $this->removeDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); $options = [ 'notnull' => ! (bool) $tableColumn['IS_NULLABLE'], 'length' => (int) $tableColumn['CHARACTER_MAXIMUM_LENGTH'], 'default' => $tableColumn['COLUMN_DEFAULT'] ?? null, 'autoincrement' => (bool) $tableColumn['IS_AUTO_INCREMENT'], 'scale' => (int) $tableColumn['NUMERIC_SCALE'], 'precision' => (int) $tableColumn['NUMERIC_PRECISION'], 'comment' => isset($tableColumn['COLUMN_COMMENT']) && $tableColumn['COLUMN_COMMENT'] !== '' ? $tableColumn['COLUMN_COMMENT'] : null, ]; $column = new Column($tableColumn['COLUMN_NAME'], Type::getType($type), $options); if (! empty($tableColumn['COLLATION_NAME'])) { $column->setPlatformOption('collation', $tableColumn['COLLATION_NAME']); } return $column; } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['SCHEMA_NAME']; } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { return $table['TABLE_NAME']; } /** * {@inheritdoc} */ public function _getPortableTableForeignKeyDefinition($tableForeignKey) { $columns = []; foreach (explode(',', $tableForeignKey['CONSTRAINT_COLUMNS']) as $value) { $columns[] = trim($value, ' `'); } $refColumns = []; foreach (explode(',', $tableForeignKey['REFERENCED_TABLE_COLUMNS']) as $value) { $refColumns[] = trim($value, ' `'); } return new ForeignKeyConstraint( $columns, $tableForeignKey['REFERENCED_TABLE_NAME'], $refColumns, $tableForeignKey['CONSTRAINT_NAME'], [ 'onUpdate' => $tableForeignKey['UPDATE_RULE'], 'onDelete' => $tableForeignKey['DELETE_RULE'], ] ); } /** * {@inheritdoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $indexes = []; foreach ($tableIndexes as $k) { $k['primary'] = (bool) $k['primary']; $indexes[] = $k; } return parent::_getPortableTableIndexesList($indexes, $tableName); } } dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php 0000644 00000025116 15120025742 0016510 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_keys; use function array_map; use function in_array; use function strrpos; use function strtolower; use function strtoupper; use function substr; /** * An abstraction class for a foreign key constraint. */ class ForeignKeyConstraint extends AbstractAsset implements Constraint { /** * Instance of the referencing table the foreign key constraint is associated with. * * @var Table */ protected $_localTable; /** * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_localColumnNames; /** * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. * * @var Table|Identifier */ protected $_foreignTableName; /** * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_foreignColumnNames; /** * Options associated with the foreign key constraint. * * @var mixed[] */ protected $_options; /** * Initializes the foreign key constraint. * * @param string[] $localColumnNames Names of the referencing table columns. * @param Table|string $foreignTableName Referenced table. * @param string[] $foreignColumnNames Names of the referenced table columns. * @param string|null $name Name of the foreign key constraint. * @param mixed[] $options Options associated with the foreign key constraint. */ public function __construct( array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = [] ) { if ($name !== null) { $this->_setName($name); } $this->_localColumnNames = $this->createIdentifierMap($localColumnNames); if ($foreignTableName instanceof Table) { $this->_foreignTableName = $foreignTableName; } else { $this->_foreignTableName = new Identifier($foreignTableName); } $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames); $this->_options = $options; } /** * @param string[] $names * * @return Identifier[] */ private function createIdentifierMap(array $names): array { $identifiers = []; foreach ($names as $name) { $identifiers[$name] = new Identifier($name); } return $identifiers; } /** * Returns the name of the referencing table * the foreign key constraint is associated with. * * @return string */ public function getLocalTableName() { return $this->_localTable->getName(); } /** * Sets the Table instance of the referencing table * the foreign key constraint is associated with. * * @param Table $table Instance of the referencing table. * * @return void */ public function setLocalTable(Table $table) { $this->_localTable = $table; } /** * @return Table */ public function getLocalTable() { return $this->_localTable; } /** * Returns the names of the referencing table columns * the foreign key constraint is associated with. * * @return string[] */ public function getLocalColumns() { return array_keys($this->_localColumnNames); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedLocalColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_localColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns unquoted representation of local table column names for comparison with other FK * * @return string[] */ public function getUnquotedLocalColumns() { return array_map([$this, 'trimQuotes'], $this->getLocalColumns()); } /** * Returns unquoted representation of foreign table column names for comparison with other FK * * @return string[] */ public function getUnquotedForeignColumns() { return array_map([$this, 'trimQuotes'], $this->getForeignColumns()); } /** * {@inheritdoc} * * @see getLocalColumns */ public function getColumns() { return $this->getLocalColumns(); } /** * Returns the quoted representation of the referencing table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referencing table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @see getQuotedLocalColumns * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedColumns(AbstractPlatform $platform) { return $this->getQuotedLocalColumns($platform); } /** * Returns the name of the referenced table * the foreign key constraint is associated with. * * @return string */ public function getForeignTableName() { return $this->_foreignTableName->getName(); } /** * Returns the non-schema qualified foreign table name. * * @return string */ public function getUnqualifiedForeignTableName() { $name = $this->_foreignTableName->getName(); $position = strrpos($name, '.'); if ($position !== false) { $name = substr($name, $position + 1); } return strtolower($name); } /** * Returns the quoted representation of the referenced table name * the foreign key constraint is associated with. * * But only if it was defined with one or the referenced table name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string */ public function getQuotedForeignTableName(AbstractPlatform $platform) { return $this->_foreignTableName->getQuotedName($platform); } /** * Returns the names of the referenced table columns * the foreign key constraint is associated with. * * @return string[] */ public function getForeignColumns() { return array_keys($this->_foreignColumnNames); } /** * Returns the quoted representation of the referenced table column names * the foreign key constraint is associated with. * * But only if they were defined with one or the referenced table column name * is a keyword reserved by the platform. * Otherwise the plain unquoted value as inserted is returned. * * @param AbstractPlatform $platform The platform to use for quotation. * * @return string[] */ public function getQuotedForeignColumns(AbstractPlatform $platform) { $columns = []; foreach ($this->_foreignColumnNames as $column) { $columns[] = $column->getQuotedName($platform); } return $columns; } /** * Returns whether or not a given option * is associated with the foreign key constraint. * * @param string $name Name of the option to check. * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * Returns an option associated with the foreign key constraint. * * @param string $name Name of the option the foreign key constraint is associated with. * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** * Returns the options associated with the foreign key constraint. * * @return mixed[] */ public function getOptions() { return $this->_options; } /** * Returns the referential action for UPDATE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onUpdate() { return $this->onEvent('onUpdate'); } /** * Returns the referential action for DELETE operations * on the referenced table the foreign key constraint is associated with. * * @return string|null */ public function onDelete() { return $this->onEvent('onDelete'); } /** * Returns the referential action for a given database operation * on the referenced table the foreign key constraint is associated with. * * @param string $event Name of the database operation/event to return the referential action for. * * @return string|null */ private function onEvent($event) { if (isset($this->_options[$event])) { $onEvent = strtoupper($this->_options[$event]); if (! in_array($onEvent, ['NO ACTION', 'RESTRICT'])) { return $onEvent; } } return null; } /** * Checks whether this foreign key constraint intersects the given index columns. * * Returns `true` if at least one of this foreign key's local columns * matches one of the given index's columns, `false` otherwise. * * @param Index $index The index to be checked against. * * @return bool */ public function intersectsIndexColumns(Index $index) { foreach ($index->getColumns() as $indexColumn) { foreach ($this->_localColumnNames as $localColumn) { if (strtolower($indexColumn) === strtolower($localColumn->getName())) { return true; } } } return false; } } dbal/lib/Doctrine/DBAL/Schema/Identifier.php 0000644 00000001232 15120025742 0014454 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * An abstraction class for an asset identifier. * * Wraps identifier names like column names in indexes / foreign keys * in an abstract class for proper quotation capabilities. */ class Identifier extends AbstractAsset { /** * @param string $identifier Identifier name to wrap. * @param bool $quote Whether to force quoting the given identifier. */ public function __construct($identifier, $quote = false) { $this->_setName($identifier); if (! $quote || $this->_quoted) { return; } $this->_setName('"' . $this->getName() . '"'); } } dbal/lib/Doctrine/DBAL/Schema/Index.php 0000644 00000021367 15120025742 0013454 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use InvalidArgumentException; use function array_filter; use function array_keys; use function array_map; use function array_search; use function array_shift; use function count; use function is_string; use function strtolower; class Index extends AbstractAsset implements Constraint { /** * Asset identifier instances of the column names the index is associated with. * array($columnName => Identifier) * * @var Identifier[] */ protected $_columns = []; /** @var bool */ protected $_isUnique = false; /** @var bool */ protected $_isPrimary = false; /** * Platform specific flags for indexes. * array($flagName => true) * * @var true[] */ protected $_flags = []; /** * Platform specific options * * @todo $_flags should eventually be refactored into options * @var mixed[] */ private $options = []; /** * @param string $name * @param string[] $columns * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options */ public function __construct( $name, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [] ) { $isUnique = $isUnique || $isPrimary; $this->_setName($name); $this->_isUnique = $isUnique; $this->_isPrimary = $isPrimary; $this->options = $options; foreach ($columns as $column) { $this->_addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** * @param string $column * * @return void * * @throws InvalidArgumentException */ protected function _addColumn($column) { if (! is_string($column)) { throw new InvalidArgumentException('Expecting a string as Index Column'); } $this->_columns[$column] = new Identifier($column); } /** * {@inheritdoc} */ public function getColumns() { return array_keys($this->_columns); } /** * {@inheritdoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') ? $this->getOption('lengths') : []; $columns = []; foreach ($this->_columns as $column) { $length = array_shift($subParts); $quotedColumn = $column->getQuotedName($platform); if ($length !== null) { $quotedColumn .= '(' . $length . ')'; } $columns[] = $quotedColumn; } return $columns; } /** * @return string[] */ public function getUnquotedColumns() { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Is the index neither unique nor primary key? * * @return bool */ public function isSimpleIndex() { return ! $this->_isPrimary && ! $this->_isUnique; } /** * @return bool */ public function isUnique() { return $this->_isUnique; } /** * @return bool */ public function isPrimary() { return $this->_isPrimary; } /** * @param string $name * @param int $pos * * @return bool */ public function hasColumnAtPosition($name, $pos = 0) { $name = $this->trimQuotes(strtolower($name)); $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); return array_search($name, $indexColumns) === $pos; } /** * Checks if this index exactly spans the given column names in the correct order. * * @param string[] $columnNames * * @return bool */ public function spansColumns(array $columnNames) { $columns = $this->getColumns(); $numberOfColumns = count($columns); $sameColumns = true; for ($i = 0; $i < $numberOfColumns; $i++) { if ( isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) ) { continue; } $sameColumns = false; } return $sameColumns; } /** * Checks if the other index already fulfills all the indexing and constraint needs of the current one. * * @return bool */ public function isFullfilledBy(Index $other) { // allow the other index to be equally large only. It being larger is an option // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) if (count($other->getColumns()) !== count($this->getColumns())) { return false; } // Check if columns are the same, and even in the same order $sameColumns = $this->spansColumns($other->getColumns()); if ($sameColumns) { if (! $this->samePartialIndex($other)) { return false; } if (! $this->hasSameColumnLengths($other)) { return false; } if (! $this->isUnique() && ! $this->isPrimary()) { // this is a special case: If the current key is neither primary or unique, any unique or // primary key will always have the same effect for the index and there cannot be any constraint // overlaps. This means a primary or unique index can always fulfill the requirements of just an // index that has no constraints. return true; } if ($other->isPrimary() !== $this->isPrimary()) { return false; } return $other->isUnique() === $this->isUnique(); } return false; } /** * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. * * @return bool */ public function overrules(Index $other) { if ($other->isPrimary()) { return false; } if ($this->isSimpleIndex() && $other->isUnique()) { return false; } return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other); } /** * Returns platform specific flags for indexes. * * @return string[] */ public function getFlags() { return array_keys($this->_flags); } /** * Adds Flag for an index that translates to platform specific handling. * * @param string $flag * * @return Index * * @example $index->addFlag('CLUSTERED') */ public function addFlag($flag) { $this->_flags[strtolower($flag)] = true; return $this; } /** * Does this index have a specific flag? * * @param string $flag * * @return bool */ public function hasFlag($flag) { return isset($this->_flags[strtolower($flag)]); } /** * Removes a flag. * * @param string $flag * * @return void */ public function removeFlag($flag) { unset($this->_flags[strtolower($flag)]); } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->options[strtolower($name)]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->options[strtolower($name)]; } /** * @return mixed[] */ public function getOptions() { return $this->options; } /** * Return whether the two indexes have the same partial index * * @return bool */ private function samePartialIndex(Index $other) { if ( $this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where') ) { return true; } return ! $this->hasOption('where') && ! $other->hasOption('where'); } /** * Returns whether the index has the same column lengths as the other */ private function hasSameColumnLengths(self $other): bool { $filter = static function (?int $length): bool { return $length !== null; }; return array_filter($this->options['lengths'] ?? [], $filter) === array_filter($other->options['lengths'] ?? [], $filter); } } dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php 0000644 00000026307 15120025742 0016065 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Types\Type; use function array_change_key_case; use function array_shift; use function assert; use function explode; use function is_string; use function preg_match; use function strpos; use function strtok; use function strtolower; use function strtr; use const CASE_LOWER; /** * Schema manager for the MySql RDBMS. */ class MySqlSchemaManager extends AbstractSchemaManager { /** * @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */ private const MARIADB_ESCAPE_SEQUENCES = [ '\\0' => "\0", "\\'" => "'", '\\"' => '"', '\\b' => "\b", '\\n' => "\n", '\\r' => "\r", '\\t' => "\t", '\\Z' => "\x1a", '\\\\' => '\\', '\\%' => '%', '\\_' => '_', // Internally, MariaDB escapes single quotes using the standard syntax "''" => "'", ]; /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { return array_shift($table); } /** * {@inheritdoc} */ protected function _getPortableUserDefinition($user) { return [ 'user' => $user['User'], 'password' => $user['Password'], ]; } /** * {@inheritdoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as $k => $v) { $v = array_change_key_case($v, CASE_LOWER); if ($v['key_name'] === 'PRIMARY') { $v['primary'] = true; } else { $v['primary'] = false; } if (strpos($v['index_type'], 'FULLTEXT') !== false) { $v['flags'] = ['FULLTEXT']; } elseif (strpos($v['index_type'], 'SPATIAL') !== false) { $v['flags'] = ['SPATIAL']; } // Ignore prohibited prefix `length` for spatial index if (strpos($v['index_type'], 'SPATIAL') === false) { $v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null; } $tableIndexes[$k] = $v; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['Database']; } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['type']); $dbType = strtok($dbType, '(), '); assert(is_string($dbType)); $length = $tableColumn['length'] ?? strtok('(), '); $fixed = null; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $scale = null; $precision = null; $type = $this->_platform->getDoctrineTypeMapping($dbType); // In cases where not connected to a database DESCRIBE $table does not return 'Comment' if (isset($tableColumn['comment'])) { $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); } switch ($dbType) { case 'char': case 'binary': $fixed = true; break; case 'float': case 'double': case 'real': case 'numeric': case 'decimal': if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'tinytext': $length = MySqlPlatform::LENGTH_LIMIT_TINYTEXT; break; case 'text': $length = MySqlPlatform::LENGTH_LIMIT_TEXT; break; case 'mediumtext': $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT; break; case 'tinyblob': $length = MySqlPlatform::LENGTH_LIMIT_TINYBLOB; break; case 'blob': $length = MySqlPlatform::LENGTH_LIMIT_BLOB; break; case 'mediumblob': $length = MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB; break; case 'tinyint': case 'smallint': case 'mediumint': case 'int': case 'integer': case 'bigint': case 'year': $length = null; break; } if ($this->_platform instanceof MariaDb1027Platform) { $columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']); } else { $columnDefault = $tableColumn['default']; } $options = [ 'length' => $length !== null ? (int) $length : null, 'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false, 'fixed' => (bool) $fixed, 'default' => $columnDefault, 'notnull' => $tableColumn['null'] !== 'YES', 'scale' => null, 'precision' => null, 'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false, 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; if ($scale !== null && $precision !== null) { $options['scale'] = (int) $scale; $options['precision'] = (int) $precision; } $column = new Column($tableColumn['field'], Type::getType($type), $options); if (isset($tableColumn['characterset'])) { $column->setPlatformOption('charset', $tableColumn['characterset']); } if (isset($tableColumn['collation'])) { $column->setPlatformOption('collation', $tableColumn['collation']); } return $column; } /** * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers. * * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted * to distinguish them from expressions (see MDEV-10134). * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema * as current_timestamp(), currdate(), currtime() * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have * null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053) * - \' is always stored as '' in information_schema (normalized) * * @link https://mariadb.com/kb/en/library/information-schema-columns-table/ * @link https://jira.mariadb.org/browse/MDEV-13132 * * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7 */ private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string { if ($columnDefault === 'NULL' || $columnDefault === null) { return null; } if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches)) { return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES); } switch ($columnDefault) { case 'current_timestamp()': return $platform->getCurrentTimestampSQL(); case 'curdate()': return $platform->getCurrentDateSQL(); case 'curtime()': return $platform->getCurrentTimeSQL(); } return $columnDefault; } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); if (! isset($list[$value['constraint_name']])) { if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') { $value['delete_rule'] = null; } if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') { $value['update_rule'] = null; } $list[$value['constraint_name']] = [ 'name' => $value['constraint_name'], 'local' => [], 'foreign' => [], 'foreignTable' => $value['referenced_table_name'], 'onDelete' => $value['delete_rule'], 'onUpdate' => $value['update_rule'], ]; } $list[$value['constraint_name']]['local'][] = $value['column_name']; $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; } $result = []; foreach ($list as $constraint) { $result[] = new ForeignKeyConstraint( $constraint['local'], $constraint['foreignTable'], $constraint['foreign'], $constraint['name'], [ 'onDelete' => $constraint['onDelete'], 'onUpdate' => $constraint['onUpdate'], ] ); } return $result; } /** * {@inheritdoc} */ public function listTableDetails($name) { $table = parent::listTableDetails($name); $platform = $this->_platform; assert($platform instanceof MySqlPlatform); $sql = $platform->getListTableMetadataSQL($name); $tableOptions = $this->_conn->fetchAssociative($sql); if ($tableOptions === false) { return $table; } $table->addOption('engine', $tableOptions['ENGINE']); if ($tableOptions['TABLE_COLLATION'] !== null) { $table->addOption('collation', $tableOptions['TABLE_COLLATION']); } if ($tableOptions['AUTO_INCREMENT'] !== null) { $table->addOption('autoincrement', $tableOptions['AUTO_INCREMENT']); } $table->addOption('comment', $tableOptions['TABLE_COMMENT']); $table->addOption('create_options', $this->parseCreateOptions($tableOptions['CREATE_OPTIONS'])); return $table; } /** * @return string[]|true[] */ private function parseCreateOptions(?string $string): array { $options = []; if ($string === null || $string === '') { return $options; } foreach (explode(' ', $string) as $pair) { $parts = explode('=', $pair, 2); $options[$parts[0]] = $parts[1] ?? true; } return $options; } } dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php 0000644 00000030473 15120025742 0016224 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Types\Type; use Throwable; use function array_change_key_case; use function array_values; use function assert; use function is_string; use function preg_match; use function sprintf; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function trim; use const CASE_LOWER; /** * Oracle Schema Manager. */ class OracleSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} */ public function dropDatabase($database) { try { parent::dropDatabase($database); } catch (DBALException $exception) { $exception = $exception->getPrevious(); assert($exception instanceof Throwable); if (! $exception instanceof Exception) { throw $exception; } // If we have a error code 1940 (ORA-01940), the drop database operation failed // because of active connections on the database. // To force dropping the database, we first have to close all active connections // on that database and issue the drop database operation again. if ($exception->getErrorCode() !== 1940) { throw $exception; } $this->killUserSessions($database); parent::dropDatabase($database); } } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, CASE_LOWER); return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']); } /** * {@inheritdoc} */ protected function _getPortableUserDefinition($user) { $user = array_change_key_case($user, CASE_LOWER); return [ 'user' => $user['username'], ]; } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { $table = array_change_key_case($table, CASE_LOWER); return $this->getQuotedIdentifierName($table['table_name']); } /** * {@inheritdoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $indexBuffer = []; foreach ($tableIndexes as $tableIndex) { $tableIndex = array_change_key_case($tableIndex, CASE_LOWER); $keyName = strtolower($tableIndex['name']); $buffer = []; if ($tableIndex['is_primary'] === 'P') { $keyName = 'primary'; $buffer['primary'] = true; $buffer['non_unique'] = false; } else { $buffer['primary'] = false; $buffer['non_unique'] = ! $tableIndex['is_unique']; } $buffer['key_name'] = $keyName; $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']); $indexBuffer[] = $buffer; } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['data_type']); if (strpos($dbType, 'timestamp(') === 0) { if (strpos($dbType, 'with time zone')) { $dbType = 'timestamptz'; } else { $dbType = 'timestamp'; } } $unsigned = $fixed = $precision = $scale = $length = null; if (! isset($tableColumn['column_name'])) { $tableColumn['column_name'] = ''; } // Default values returned from database sometimes have trailing spaces. if (is_string($tableColumn['data_default'])) { $tableColumn['data_default'] = trim($tableColumn['data_default']); } if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') { $tableColumn['data_default'] = null; } if ($tableColumn['data_default'] !== null) { // Default values returned from database are represented as literal expressions if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches)) { $tableColumn['data_default'] = str_replace("''", "'", $matches[1]); } } if ($tableColumn['data_precision'] !== null) { $precision = (int) $tableColumn['data_precision']; } if ($tableColumn['data_scale'] !== null) { $scale = (int) $tableColumn['data_scale']; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type); $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type); switch ($dbType) { case 'number': if ($precision === 20 && $scale === 0) { $type = 'bigint'; } elseif ($precision === 5 && $scale === 0) { $type = 'smallint'; } elseif ($precision === 1 && $scale === 0) { $type = 'boolean'; } elseif ($scale > 0) { $type = 'decimal'; } break; case 'varchar': case 'varchar2': case 'nvarchar2': $length = $tableColumn['char_length']; $fixed = false; break; case 'char': case 'nchar': $length = $tableColumn['char_length']; $fixed = true; break; } $options = [ 'notnull' => $tableColumn['nullable'] === 'N', 'fixed' => (bool) $fixed, 'unsigned' => (bool) $unsigned, 'default' => $tableColumn['data_default'], 'length' => $length, 'precision' => $precision, 'scale' => $scale, 'comment' => isset($tableColumn['comments']) && $tableColumn['comments'] !== '' ? $tableColumn['comments'] : null, ]; return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); if (! isset($list[$value['constraint_name']])) { if ($value['delete_rule'] === 'NO ACTION') { $value['delete_rule'] = null; } $list[$value['constraint_name']] = [ 'name' => $this->getQuotedIdentifierName($value['constraint_name']), 'local' => [], 'foreign' => [], 'foreignTable' => $value['references_table'], 'onDelete' => $value['delete_rule'], ]; } $localColumn = $this->getQuotedIdentifierName($value['local_column']); $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']); $list[$value['constraint_name']]['local'][$value['position']] = $localColumn; $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn; } $result = []; foreach ($list as $constraint) { $result[] = new ForeignKeyConstraint( array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']), array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']), ['onDelete' => $constraint['onDelete']] ); } return $result; } /** * {@inheritdoc} */ protected function _getPortableSequenceDefinition($sequence) { $sequence = array_change_key_case($sequence, CASE_LOWER); return new Sequence( $this->getQuotedIdentifierName($sequence['sequence_name']), (int) $sequence['increment_by'], (int) $sequence['min_value'] ); } /** * {@inheritdoc} * * @deprecated */ protected function _getPortableFunctionDefinition($function) { $function = array_change_key_case($function, CASE_LOWER); return $function['name']; } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { $database = array_change_key_case($database, CASE_LOWER); return $database['username']; } /** * {@inheritdoc} * * @param string|null $database * * Calling this method without an argument or by passing NULL is deprecated. */ public function createDatabase($database = null) { if ($database === null) { $database = $this->_conn->getDatabase(); } $statement = 'CREATE USER ' . $database; $params = $this->_conn->getParams(); if (isset($params['password'])) { $statement .= ' IDENTIFIED BY ' . $params['password']; } $this->_conn->executeStatement($statement); $statement = 'GRANT DBA TO ' . $database; $this->_conn->executeStatement($statement); } /** * @param string $table * * @return bool */ public function dropAutoincrement($table) { assert($this->_platform instanceof OraclePlatform); $sql = $this->_platform->getDropAutoincrementSql($table); foreach ($sql as $query) { $this->_conn->executeStatement($query); } return true; } /** * {@inheritdoc} */ public function dropTable($name) { $this->tryMethod('dropAutoincrement', $name); parent::dropTable($name); } /** * Returns the quoted representation of the given identifier name. * * Quotes non-uppercase identifiers explicitly to preserve case * and thus make references to the particular identifier work. * * @param string $identifier The identifier to quote. * * @return string The quoted identifier. */ private function getQuotedIdentifierName($identifier) { if (preg_match('/[a-z]/', $identifier)) { return $this->_platform->quoteIdentifier($identifier); } return $identifier; } /** * Kills sessions connected with the given user. * * This is useful to force DROP USER operations which could fail because of active user sessions. * * @param string $user The name of the user to kill sessions for. * * @return void */ private function killUserSessions($user) { $sql = <<<SQL SELECT s.sid, s.serial# FROM gv\$session s, gv\$process p WHERE s.username = ? AND p.addr(+) = s.paddr SQL; $activeUserSessions = $this->_conn->fetchAllAssociative($sql, [strtoupper($user)]); foreach ($activeUserSessions as $activeUserSession) { $activeUserSession = array_change_key_case($activeUserSession, CASE_LOWER); $this->_execSql( sprintf( "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE", $activeUserSession['sid'], $activeUserSession['serial#'] ) ); } } /** * {@inheritdoc} */ public function listTableDetails($name): Table { $table = parent::listTableDetails($name); $platform = $this->_platform; assert($platform instanceof OraclePlatform); $sql = $platform->getListTableCommentsSQL($name); $tableOptions = $this->_conn->fetchAssociative($sql); if ($tableOptions !== false) { $table->addOption('comment', $tableOptions['COMMENTS']); } return $table; } } dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php 0000644 00000040232 15120025742 0017114 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use function array_change_key_case; use function array_filter; use function array_keys; use function array_map; use function array_shift; use function assert; use function explode; use function implode; use function in_array; use function preg_match; use function preg_replace; use function sprintf; use function str_replace; use function strpos; use function strtolower; use function trim; use const CASE_LOWER; /** * PostgreSQL Schema Manager. */ class PostgreSqlSchemaManager extends AbstractSchemaManager { /** @var string[]|null */ private $existingSchemaPaths; /** * Gets all the existing schema names. * * @return string[] */ public function getSchemaNames() { $statement = $this->_conn->executeQuery( "SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'" ); return $statement->fetchAll(FetchMode::COLUMN); } /** * Returns an array of schema search paths. * * This is a PostgreSQL only function. * * @return string[] */ public function getSchemaSearchPaths() { $params = $this->_conn->getParams(); $searchPaths = $this->_conn->fetchColumn('SHOW search_path'); assert($searchPaths !== false); $schema = explode(',', $searchPaths); if (isset($params['user'])) { $schema = str_replace('"$user"', $params['user'], $schema); } return array_map('trim', $schema); } /** * Gets names of all existing schemas in the current users search path. * * This is a PostgreSQL only function. * * @return string[] */ public function getExistingSchemaSearchPaths() { if ($this->existingSchemaPaths === null) { $this->determineExistingSchemaSearchPaths(); } assert($this->existingSchemaPaths !== null); return $this->existingSchemaPaths; } /** * Sets or resets the order of the existing schemas in the current search path of the user. * * This is a PostgreSQL only function. * * @return void */ public function determineExistingSchemaSearchPaths() { $names = $this->getSchemaNames(); $paths = $this->getSchemaSearchPaths(); $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names) { return in_array($v, $names); }); } /** * {@inheritdoc} */ public function dropDatabase($database) { try { parent::dropDatabase($database); } catch (DriverException $exception) { // If we have a SQLSTATE 55006, the drop database operation failed // because of active connections on the database. // To force dropping the database, we first have to close all active connections // on that database and issue the drop database operation again. if ($exception->getSQLState() !== '55006') { throw $exception; } assert($this->_platform instanceof PostgreSqlPlatform); $this->_execSql( [ $this->_platform->getDisallowDatabaseConnectionsSQL($database), $this->_platform->getCloseActiveDatabaseConnectionsSQL($database), ] ); parent::dropDatabase($database); } } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { $onUpdate = null; $onDelete = null; $localColumns = []; $foreignColumns = []; $foreignTable = null; if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { $onUpdate = $match[1]; } if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { $onDelete = $match[1]; } $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values); assert($result === 1); // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get // the idea to trim them here. $localColumns = array_map('trim', explode(',', $values[1])); $foreignColumns = array_map('trim', explode(',', $values[3])); $foreignTable = $values[2]; return new ForeignKeyConstraint( $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], ['onUpdate' => $onUpdate, 'onDelete' => $onDelete] ); } /** * {@inheritdoc} */ protected function _getPortableTriggerDefinition($trigger) { return $trigger['trigger_name']; } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']); } /** * {@inheritdoc} */ protected function _getPortableUserDefinition($user) { return [ 'user' => $user['usename'], 'password' => $user['passwd'], ]; } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { $schemas = $this->getExistingSchemaSearchPaths(); $firstSchema = array_shift($schemas); if ($table['schema_name'] === $firstSchema) { return $table['table_name']; } return $table['schema_name'] . '.' . $table['table_name']; } /** * {@inheritdoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $buffer = []; foreach ($tableIndexes as $row) { $colNumbers = array_map('intval', explode(' ', $row['indkey'])); $columnNameSql = sprintf( 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', $row['indrelid'], implode(' ,', $colNumbers) ); $indexColumns = $this->_conn->fetchAllAssociative($columnNameSql); // required for getting the order of the columns right. foreach ($colNumbers as $colNum) { foreach ($indexColumns as $colRow) { if ($colNum !== $colRow['attnum']) { continue; } $buffer[] = [ 'key_name' => $row['relname'], 'column_name' => trim($colRow['attname']), 'non_unique' => ! $row['indisunique'], 'primary' => $row['indisprimary'], 'where' => $row['where'], ]; } } } return parent::_getPortableTableIndexesList($buffer, $tableName); } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['datname']; } /** * {@inheritdoc} */ protected function _getPortableSequencesList($sequences) { $sequenceDefinitions = []; foreach ($sequences as $sequence) { if ($sequence['schemaname'] !== 'public') { $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; } else { $sequenceName = $sequence['relname']; } $sequenceDefinitions[$sequenceName] = $sequence; } $list = []; foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) { $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]); } return $list; } /** * {@inheritdoc} */ protected function getPortableNamespaceDefinition(array $namespace) { return $namespace['nspname']; } /** * {@inheritdoc} */ protected function _getPortableSequenceDefinition($sequence) { if ($sequence['schemaname'] !== 'public') { $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; } else { $sequenceName = $sequence['relname']; } if (! isset($sequence['increment_by'], $sequence['min_value'])) { /** @var string[] $data */ $data = $this->_conn->fetchAssoc( 'SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName) ); $sequence += $data; } return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']); } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') { // get length from varchar definition $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); $tableColumn['length'] = $length; } $matches = []; $autoincrement = false; if ( $tableColumn['default'] !== null && preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches) === 1 ) { $tableColumn['sequence'] = $matches[1]; $tableColumn['default'] = null; $autoincrement = true; } if ($tableColumn['default'] !== null) { if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) { $tableColumn['default'] = $matches[1]; } elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) { $tableColumn['default'] = null; } } $length = $tableColumn['length'] ?? null; if ($length === '-1' && isset($tableColumn['atttypmod'])) { $length = $tableColumn['atttypmod'] - 4; } if ((int) $length <= 0) { $length = null; } $fixed = null; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; $jsonb = null; $dbType = strtolower($tableColumn['type']); if ( $tableColumn['domain_type'] !== null && $tableColumn['domain_type'] !== '' && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type']) ) { $dbType = strtolower($tableColumn['domain_type']); $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); switch ($dbType) { case 'smallint': case 'int2': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'int': case 'int4': case 'integer': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'bigint': case 'int8': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); $length = null; break; case 'bool': case 'boolean': if ($tableColumn['default'] === 'true') { $tableColumn['default'] = true; } if ($tableColumn['default'] === 'false') { $tableColumn['default'] = false; } $length = null; break; case 'text': case '_varchar': case 'varchar': $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); $fixed = false; break; case 'interval': $fixed = false; break; case 'char': case 'bpchar': $fixed = true; break; case 'float': case 'float4': case 'float8': case 'double': case 'double precision': case 'real': case 'decimal': case 'money': case 'numeric': $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'year': $length = null; break; // PostgreSQL 9.4+ only case 'jsonb': $jsonb = true; break; } if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) { $tableColumn['default'] = $match[1]; } $options = [ 'length' => $length, 'notnull' => (bool) $tableColumn['isnotnull'], 'default' => $tableColumn['default'], 'precision' => $precision, 'scale' => $scale, 'fixed' => $fixed, 'unsigned' => false, 'autoincrement' => $autoincrement, 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; $column = new Column($tableColumn['field'], Type::getType($type), $options); if (isset($tableColumn['collation']) && ! empty($tableColumn['collation'])) { $column->setPlatformOption('collation', $tableColumn['collation']); } if (in_array($column->getType()->getName(), [Types::JSON_ARRAY, Types::JSON], true)) { $column->setPlatformOption('jsonb', $jsonb); } return $column; } /** * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually. * * @param mixed $defaultValue * * @return mixed */ private function fixVersion94NegativeNumericDefaultValue($defaultValue) { if ($defaultValue !== null && strpos($defaultValue, '(') === 0) { return trim($defaultValue, '()'); } return $defaultValue; } /** * Parses a default value expression as given by PostgreSQL */ private function parseDefaultExpression(?string $default): ?string { if ($default === null) { return $default; } return str_replace("''", "'", $default); } /** * {@inheritdoc} */ public function listTableDetails($name): Table { $table = parent::listTableDetails($name); $platform = $this->_platform; assert($platform instanceof PostgreSqlPlatform); $sql = $platform->getListTableMetadataSQL($name); $tableOptions = $this->_conn->fetchAssoc($sql); if ($tableOptions !== false) { $table->addOption('comment', $tableOptions['table_comment']); } return $table; } } dbal/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php 0000644 00000016315 15120025742 0017160 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\SQLAnywherePlatform; use Doctrine\DBAL\Types\Type; use function assert; use function preg_replace; /** * SAP Sybase SQL Anywhere schema manager. */ class SQLAnywhereSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} * * Starts a database after creation * as SQL Anywhere needs a database to be started * before it can be used. * * @see startDatabase */ public function createDatabase($database) { parent::createDatabase($database); $this->startDatabase($database); } /** * {@inheritdoc} * * Tries stopping a database before dropping * as SQL Anywhere needs a database to be stopped * before it can be dropped. * * @see stopDatabase */ public function dropDatabase($database) { $this->tryMethod('stopDatabase', $database); parent::dropDatabase($database); } /** * Starts a database. * * @param string $database The name of the database to start. * * @return void */ public function startDatabase($database) { assert($this->_platform instanceof SQLAnywherePlatform); $this->_execSql($this->_platform->getStartDatabaseSQL($database)); } /** * Stops a database. * * @param string $database The name of the database to stop. * * @return void */ public function stopDatabase($database) { assert($this->_platform instanceof SQLAnywherePlatform); $this->_execSql($this->_platform->getStopDatabaseSQL($database)); } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['name']; } /** * {@inheritdoc} */ protected function _getPortableSequenceDefinition($sequence) { return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['start_with']); } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $type = $this->_platform->getDoctrineTypeMapping($tableColumn['type']); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); $precision = null; $scale = null; $fixed = false; $default = null; if ($tableColumn['default'] !== null) { // Strip quotes from default value. $default = preg_replace(["/^'(.*)'$/", "/''/"], ['$1', "'"], $tableColumn['default']); if ($default === 'autoincrement') { $default = null; } } switch ($tableColumn['type']) { case 'binary': case 'char': case 'nchar': $fixed = true; break; } switch ($type) { case 'decimal': case 'float': $precision = $tableColumn['length']; $scale = $tableColumn['scale']; break; } return new Column( $tableColumn['column_name'], Type::getType($type), [ 'length' => $type === 'string' ? $tableColumn['length'] : null, 'precision' => $precision, 'scale' => $scale, 'unsigned' => (bool) $tableColumn['unsigned'], 'fixed' => $fixed, 'notnull' => (bool) $tableColumn['notnull'], 'default' => $default, 'autoincrement' => (bool) $tableColumn['autoincrement'], 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ] ); } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { return $table['table_name']; } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'] ); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { if (! isset($foreignKeys[$tableForeignKey['index_name']])) { $foreignKeys[$tableForeignKey['index_name']] = [ 'local_columns' => [$tableForeignKey['local_column']], 'foreign_table' => $tableForeignKey['foreign_table'], 'foreign_columns' => [$tableForeignKey['foreign_column']], 'name' => $tableForeignKey['index_name'], 'options' => [ 'notnull' => $tableForeignKey['notnull'], 'match' => $tableForeignKey['match'], 'onUpdate' => $tableForeignKey['on_update'], 'onDelete' => $tableForeignKey['on_delete'], 'check_on_commit' => $tableForeignKey['check_on_commit'], 'clustered' => $tableForeignKey['clustered'], 'for_olap_workload' => $tableForeignKey['for_olap_workload'], ], ]; } else { $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * {@inheritdoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndex) { $tableIndex['primary'] = (bool) $tableIndex['primary']; $tableIndex['flags'] = []; if ($tableIndex['clustered']) { $tableIndex['flags'][] = 'clustered'; } if ($tableIndex['with_nulls_not_distinct']) { $tableIndex['flags'][] = 'with_nulls_not_distinct'; } if (! $tableIndex['for_olap_workload']) { continue; } $tableIndex['flags'][] = 'for_olap_workload'; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { $definition = preg_replace('/^.*\s+as\s+SELECT(.*)/i', 'SELECT$1', $view['view_def']); return new View($view['table_name'], $definition); } } dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php 0000644 00000024736 15120025742 0016652 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Types\Type; use Throwable; use function assert; use function count; use function in_array; use function is_string; use function preg_match; use function sprintf; use function str_replace; use function strpos; use function strtok; /** * SQL Server Schema Manager. */ class SQLServerSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} */ public function dropDatabase($database) { try { parent::dropDatabase($database); } catch (DBALException $exception) { $exception = $exception->getPrevious(); assert($exception instanceof Throwable); if (! $exception instanceof Exception) { throw $exception; } // If we have a error code 3702, the drop database operation failed // because of active connections on the database. // To force dropping the database, we first have to close all active connections // on that database and issue the drop database operation again. if ($exception->getErrorCode() !== 3702) { throw $exception; } $this->closeActiveDatabaseConnections($database); parent::dropDatabase($database); } } /** * {@inheritdoc} */ protected function _getPortableSequenceDefinition($sequence) { return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']); } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtok($tableColumn['type'], '(), '); assert(is_string($dbType)); $fixed = null; $length = (int) $tableColumn['length']; $default = $tableColumn['default']; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } if ($default !== null) { $default = $this->parseDefaultExpression($default); } switch ($dbType) { case 'nchar': case 'nvarchar': case 'ntext': // Unicode data requires 2 bytes per character $length /= 2; break; case 'varchar': // TEXT type is returned as VARCHAR(MAX) with a length of -1 if ($length === -1) { $dbType = 'text'; } break; } if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') { $fixed = true; } $type = $this->_platform->getDoctrineTypeMapping($dbType); $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); $options = [ 'length' => $length === 0 || ! in_array($type, ['text', 'string']) ? null : $length, 'unsigned' => false, 'fixed' => (bool) $fixed, 'default' => $default, 'notnull' => (bool) $tableColumn['notnull'], 'scale' => $tableColumn['scale'], 'precision' => $tableColumn['precision'], 'autoincrement' => (bool) $tableColumn['autoincrement'], 'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null, ]; $column = new Column($tableColumn['name'], Type::getType($type), $options); if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { $column->setPlatformOption('collation', $tableColumn['collation']); } return $column; } private function parseDefaultExpression(string $value): ?string { while (preg_match('/^\((.*)\)$/s', $value, $matches)) { $value = $matches[1]; } if ($value === 'NULL') { return null; } if (preg_match('/^\'(.*)\'$/s', $value, $matches)) { $value = str_replace("''", "'", $matches[1]); } if ($value === 'getdate()') { return $this->_platform->getCurrentTimestampSQL(); } return $value; } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $foreignKeys = []; foreach ($tableForeignKeys as $tableForeignKey) { $name = $tableForeignKey['ForeignKey']; if (! isset($foreignKeys[$name])) { $foreignKeys[$name] = [ 'local_columns' => [$tableForeignKey['ColumnName']], 'foreign_table' => $tableForeignKey['ReferenceTableName'], 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], 'name' => $name, 'options' => [ 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), ], ]; } else { $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName']; $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; } } return parent::_getPortableTableForeignKeysList($foreignKeys); } /** * {@inheritdoc} */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { foreach ($tableIndexes as &$tableIndex) { $tableIndex['non_unique'] = (bool) $tableIndex['non_unique']; $tableIndex['primary'] = (bool) $tableIndex['primary']; $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( $tableForeignKey['local_columns'], $tableForeignKey['foreign_table'], $tableForeignKey['foreign_columns'], $tableForeignKey['name'], $tableForeignKey['options'] ); } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { if (isset($table['schema_name']) && $table['schema_name'] !== 'dbo') { return $table['schema_name'] . '.' . $table['name']; } return $table['name']; } /** * {@inheritdoc} */ protected function _getPortableDatabaseDefinition($database) { return $database['name']; } /** * {@inheritdoc} */ protected function getPortableNamespaceDefinition(array $namespace) { return $namespace['name']; } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { // @todo return new View($view['name'], ''); } /** * {@inheritdoc} */ public function listTableIndexes($table) { $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); try { $tableIndexes = $this->_conn->fetchAllAssociative($sql); } catch (DBALException $e) { if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) { return []; } throw $e; } return $this->_getPortableTableIndexesList($tableIndexes, $table); } /** * {@inheritdoc} */ public function alterTable(TableDiff $tableDiff) { if (count($tableDiff->removedColumns) > 0) { foreach ($tableDiff->removedColumns as $col) { $columnConstraintSql = $this->getColumnConstraintSQL($tableDiff->name, $col->getName()); foreach ($this->_conn->fetchAllAssociative($columnConstraintSql) as $constraint) { $this->_conn->exec( sprintf( 'ALTER TABLE %s DROP CONSTRAINT %s', $tableDiff->name, $constraint['Name'] ) ); } } } parent::alterTable($tableDiff); } /** * Returns the SQL to retrieve the constraints for a given column. * * @param string $table * @param string $column * * @return string */ private function getColumnConstraintSQL($table, $column) { return "SELECT sysobjects.[Name] FROM sysobjects INNER JOIN (SELECT [Name],[ID] FROM sysobjects WHERE XType = 'U') AS Tab ON Tab.[ID] = sysobjects.[Parent_Obj] INNER JOIN sys.default_constraints DefCons ON DefCons.[object_id] = sysobjects.[ID] INNER JOIN syscolumns Col ON Col.[ColID] = DefCons.[parent_column_id] AND Col.[ID] = Tab.[ID] WHERE Col.[Name] = " . $this->_conn->quote($column) . ' AND Tab.[Name] = ' . $this->_conn->quote($table) . ' ORDER BY Col.[Name]'; } /** * Closes currently active connections on the given database. * * This is useful to force DROP DATABASE operations which could fail because of active connections. * * @param string $database The name of the database to close currently active connections for. * * @return void */ private function closeActiveDatabaseConnections($database) { $database = new Identifier($database); $this->_execSql( sprintf( 'ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE', $database->getQuotedName($this->_platform) ) ); } /** * @param string $name */ public function listTableDetails($name): Table { $table = parent::listTableDetails($name); $platform = $this->_platform; assert($platform instanceof SQLServerPlatform); $sql = $platform->getListTableMetadataSQL($name); $tableOptions = $this->_conn->fetchAssociative($sql); if ($tableOptions !== false) { $table->addOption('comment', $tableOptions['table_comment']); } return $table; } } dbal/lib/Doctrine/DBAL/Schema/Schema.php 0000644 00000027104 15120025742 0013600 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector; use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor; use Doctrine\DBAL\Schema\Visitor\Visitor; use function array_keys; use function strpos; use function strtolower; /** * Object representation of a database schema. * * Different vendors have very inconsistent naming with regard to the concept * of a "schema". Doctrine understands a schema as the entity that conceptually * wraps a set of database objects such as tables, sequences, indexes and * foreign keys that belong to each other into a namespace. A Doctrine Schema * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. * * Every asset in the doctrine schema has a name. A name consists of either a * namespace.local name pair or just a local unqualified name. * * The abstraction layer that covers a PostgreSQL schema is the namespace of an * database object (asset). A schema can have a name, which will be used as * default namespace for the unqualified database objects that are created in * the schema. * * In the case of MySQL where cross-database queries are allowed this leads to * databases being "misinterpreted" as namespaces. This is intentional, however * the CREATE/DROP SQL visitors will just filter this queries and do not * execute them. Only the queries for the currently connected database are * executed. */ class Schema extends AbstractAsset { /** * The namespaces in this schema. * * @var string[] */ private $namespaces = []; /** @var Table[] */ protected $_tables = []; /** @var Sequence[] */ protected $_sequences = []; /** @var SchemaConfig */ protected $_schemaConfig; /** * @param Table[] $tables * @param Sequence[] $sequences * @param string[] $namespaces */ public function __construct( array $tables = [], array $sequences = [], ?SchemaConfig $schemaConfig = null, array $namespaces = [] ) { if ($schemaConfig === null) { $schemaConfig = new SchemaConfig(); } $this->_schemaConfig = $schemaConfig; $this->_setName($schemaConfig->getName() ?: 'public'); foreach ($namespaces as $namespace) { $this->createNamespace($namespace); } foreach ($tables as $table) { $this->_addTable($table); } foreach ($sequences as $sequence) { $this->_addSequence($sequence); } } /** * @return bool */ public function hasExplicitForeignKeyIndexes() { return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); } /** * @return void * * @throws SchemaException */ protected function _addTable(Table $table) { $namespaceName = $table->getNamespaceName(); $tableName = $table->getFullQualifiedName($this->getName()); if (isset($this->_tables[$tableName])) { throw SchemaException::tableAlreadyExists($tableName); } if ( $namespaceName !== null && ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_tables[$tableName] = $table; $table->setSchemaConfig($this->_schemaConfig); } /** * @return void * * @throws SchemaException */ protected function _addSequence(Sequence $sequence) { $namespaceName = $sequence->getNamespaceName(); $seqName = $sequence->getFullQualifiedName($this->getName()); if (isset($this->_sequences[$seqName])) { throw SchemaException::sequenceAlreadyExists($seqName); } if ( $namespaceName !== null && ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName) ) { $this->createNamespace($namespaceName); } $this->_sequences[$seqName] = $sequence; } /** * Returns the namespaces of this schema. * * @return string[] A list of namespace names. */ public function getNamespaces() { return $this->namespaces; } /** * Gets all tables of this schema. * * @return Table[] */ public function getTables() { return $this->_tables; } /** * @param string $name * * @return Table * * @throws SchemaException */ public function getTable($name) { $name = $this->getFullQualifiedAssetName($name); if (! isset($this->_tables[$name])) { throw SchemaException::tableDoesNotExist($name); } return $this->_tables[$name]; } /** * @param string $name * * @return string */ private function getFullQualifiedAssetName($name) { $name = $this->getUnquotedAssetName($name); if (strpos($name, '.') === false) { $name = $this->getName() . '.' . $name; } return strtolower($name); } /** * Returns the unquoted representation of a given asset name. * * @param string $assetName Quoted or unquoted representation of an asset name. * * @return string */ private function getUnquotedAssetName($assetName) { if ($this->isIdentifierQuoted($assetName)) { return $this->trimQuotes($assetName); } return $assetName; } /** * Does this schema have a namespace with the given name? * * @param string $name * * @return bool */ public function hasNamespace($name) { $name = strtolower($this->getUnquotedAssetName($name)); return isset($this->namespaces[$name]); } /** * Does this schema have a table with the given name? * * @param string $name * * @return bool */ public function hasTable($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_tables[$name]); } /** * Gets all table names, prefixed with a schema name, even the default one if present. * * @return string[] */ public function getTableNames() { return array_keys($this->_tables); } /** * @param string $name * * @return bool */ public function hasSequence($name) { $name = $this->getFullQualifiedAssetName($name); return isset($this->_sequences[$name]); } /** * @param string $name * * @return Sequence * * @throws SchemaException */ public function getSequence($name) { $name = $this->getFullQualifiedAssetName($name); if (! $this->hasSequence($name)) { throw SchemaException::sequenceDoesNotExist($name); } return $this->_sequences[$name]; } /** * @return Sequence[] */ public function getSequences() { return $this->_sequences; } /** * Creates a new namespace. * * @param string $name The name of the namespace to create. * * @return Schema This schema instance. * * @throws SchemaException */ public function createNamespace($name) { $unquotedName = strtolower($this->getUnquotedAssetName($name)); if (isset($this->namespaces[$unquotedName])) { throw SchemaException::namespaceAlreadyExists($unquotedName); } $this->namespaces[$unquotedName] = $name; return $this; } /** * Creates a new table. * * @param string $name * * @return Table */ public function createTable($name) { $table = new Table($name); $this->_addTable($table); foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) { $table->addOption($option, $value); } return $table; } /** * Renames a table. * * @param string $oldName * @param string $newName * * @return Schema */ public function renameTable($oldName, $newName) { $table = $this->getTable($oldName); $table->_setName($newName); $this->dropTable($oldName); $this->_addTable($table); return $this; } /** * Drops a table from the schema. * * @param string $name * * @return Schema */ public function dropTable($name) { $name = $this->getFullQualifiedAssetName($name); $this->getTable($name); unset($this->_tables[$name]); return $this; } /** * Creates a new sequence. * * @param string $name * @param int $allocationSize * @param int $initialValue * * @return Sequence */ public function createSequence($name, $allocationSize = 1, $initialValue = 1) { $seq = new Sequence($name, $allocationSize, $initialValue); $this->_addSequence($seq); return $seq; } /** * @param string $name * * @return Schema */ public function dropSequence($name) { $name = $this->getFullQualifiedAssetName($name); unset($this->_sequences[$name]); return $this; } /** * Returns an array of necessary SQL queries to create the schema on the given platform. * * @return string[] */ public function toSql(AbstractPlatform $platform) { $sqlCollector = new CreateSchemaSqlCollector($platform); $this->visit($sqlCollector); return $sqlCollector->getQueries(); } /** * Return an array of necessary SQL queries to drop the schema on the given platform. * * @return string[] */ public function toDropSql(AbstractPlatform $platform) { $dropSqlCollector = new DropSchemaSqlCollector($platform); $this->visit($dropSqlCollector); return $dropSqlCollector->getQueries(); } /** * @return string[] */ public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) { $comparator = new Comparator(); $schemaDiff = $comparator->compare($this, $toSchema); return $schemaDiff->toSql($platform); } /** * @return string[] */ public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) { $comparator = new Comparator(); $schemaDiff = $comparator->compare($fromSchema, $this); return $schemaDiff->toSql($platform); } /** * @return void */ public function visit(Visitor $visitor) { $visitor->acceptSchema($this); if ($visitor instanceof NamespaceVisitor) { foreach ($this->namespaces as $namespace) { $visitor->acceptNamespace($namespace); } } foreach ($this->_tables as $table) { $table->visit($visitor); } foreach ($this->_sequences as $sequence) { $sequence->visit($visitor); } } /** * Cloning a Schema triggers a deep clone of all related assets. * * @return void */ public function __clone() { foreach ($this->_tables as $k => $table) { $this->_tables[$k] = clone $table; } foreach ($this->_sequences as $k => $sequence) { $this->_sequences[$k] = clone $sequence; } } } dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php 0000644 00000003560 15120025742 0014726 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * Configuration for a Schema. */ class SchemaConfig { /** @var bool */ protected $hasExplicitForeignKeyIndexes = false; /** @var int */ protected $maxIdentifierLength = 63; /** @var string */ protected $name; /** @var mixed[] */ protected $defaultTableOptions = []; /** * @return bool */ public function hasExplicitForeignKeyIndexes() { return $this->hasExplicitForeignKeyIndexes; } /** * @param bool $flag * * @return void */ public function setExplicitForeignKeyIndexes($flag) { $this->hasExplicitForeignKeyIndexes = (bool) $flag; } /** * @param int $length * * @return void */ public function setMaxIdentifierLength($length) { $this->maxIdentifierLength = (int) $length; } /** * @return int */ public function getMaxIdentifierLength() { return $this->maxIdentifierLength; } /** * Gets the default namespace of schema objects. * * @return string */ public function getName() { return $this->name; } /** * Sets the default namespace name of schema objects. * * @param string $name The value to set. * * @return void */ public function setName($name) { $this->name = $name; } /** * Gets the default options that are passed to Table instances created with * Schema#createTable(). * * @return mixed[] */ public function getDefaultTableOptions() { return $this->defaultTableOptions; } /** * @param mixed[] $defaultTableOptions * * @return void */ public function setDefaultTableOptions(array $defaultTableOptions) { $this->defaultTableOptions = $defaultTableOptions; } } dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php 0000644 00000010327 15120025742 0014370 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; use function array_merge; /** * Schema Diff. */ class SchemaDiff { /** @var Schema|null */ public $fromSchema; /** * All added namespaces. * * @var string[] */ public $newNamespaces = []; /** * All removed namespaces. * * @var string[] */ public $removedNamespaces = []; /** * All added tables. * * @var Table[] */ public $newTables = []; /** * All changed tables. * * @var TableDiff[] */ public $changedTables = []; /** * All removed tables. * * @var Table[] */ public $removedTables = []; /** @var Sequence[] */ public $newSequences = []; /** @var Sequence[] */ public $changedSequences = []; /** @var Sequence[] */ public $removedSequences = []; /** @var ForeignKeyConstraint[] */ public $orphanedForeignKeys = []; /** * Constructs an SchemaDiff object. * * @param Table[] $newTables * @param TableDiff[] $changedTables * @param Table[] $removedTables */ public function __construct($newTables = [], $changedTables = [], $removedTables = [], ?Schema $fromSchema = null) { $this->newTables = $newTables; $this->changedTables = $changedTables; $this->removedTables = $removedTables; $this->fromSchema = $fromSchema; } /** * The to save sql mode ensures that the following things don't happen: * * 1. Tables are deleted * 2. Sequences are deleted * 3. Foreign Keys which reference tables that would otherwise be deleted. * * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. * * @return string[] */ public function toSaveSql(AbstractPlatform $platform) { return $this->_toSql($platform, true); } /** * @return string[] */ public function toSql(AbstractPlatform $platform) { return $this->_toSql($platform, false); } /** * @param bool $saveMode * * @return string[] */ protected function _toSql(AbstractPlatform $platform, $saveMode = false) { $sql = []; if ($platform->supportsSchemas()) { foreach ($this->newNamespaces as $newNamespace) { $sql[] = $platform->getCreateSchemaSQL($newNamespace); } } if ($platform->supportsForeignKeyConstraints() && $saveMode === false) { foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable()); } } if ($platform->supportsSequences() === true) { foreach ($this->changedSequences as $sequence) { $sql[] = $platform->getAlterSequenceSQL($sequence); } if ($saveMode === false) { foreach ($this->removedSequences as $sequence) { $sql[] = $platform->getDropSequenceSQL($sequence); } } foreach ($this->newSequences as $sequence) { $sql[] = $platform->getCreateSequenceSQL($sequence); } } $foreignKeySql = []; foreach ($this->newTables as $table) { $sql = array_merge( $sql, $platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES) ); if (! $platform->supportsForeignKeyConstraints()) { continue; } foreach ($table->getForeignKeys() as $foreignKey) { $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table); } } $sql = array_merge($sql, $foreignKeySql); if ($saveMode === false) { foreach ($this->removedTables as $table) { $sql[] = $platform->getDropTableSQL($table); } } foreach ($this->changedTables as $tableDiff) { $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); } return $sql; } } dbal/lib/Doctrine/DBAL/Schema/SchemaException.php 0000644 00000012010 15120025742 0015445 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use function implode; use function sprintf; /** * @psalm-immutable */ class SchemaException extends Exception { public const TABLE_DOESNT_EXIST = 10; public const TABLE_ALREADY_EXISTS = 20; public const COLUMN_DOESNT_EXIST = 30; public const COLUMN_ALREADY_EXISTS = 40; public const INDEX_DOESNT_EXIST = 50; public const INDEX_ALREADY_EXISTS = 60; public const SEQUENCE_DOENST_EXIST = 70; public const SEQUENCE_ALREADY_EXISTS = 80; public const INDEX_INVALID_NAME = 90; public const FOREIGNKEY_DOESNT_EXIST = 100; public const NAMESPACE_ALREADY_EXISTS = 110; /** * @param string $tableName * * @return SchemaException */ public static function tableDoesNotExist($tableName) { return new self("There is no table with name '" . $tableName . "' in the schema.", self::TABLE_DOESNT_EXIST); } /** * @param string $indexName * * @return SchemaException */ public static function indexNameInvalid($indexName) { return new self( sprintf('Invalid index-name %s given, has to be [a-zA-Z0-9_]', $indexName), self::INDEX_INVALID_NAME ); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexDoesNotExist($indexName, $table) { return new self( sprintf("Index '%s' does not exist on table '%s'.", $indexName, $table), self::INDEX_DOESNT_EXIST ); } /** * @param string $indexName * @param string $table * * @return SchemaException */ public static function indexAlreadyExists($indexName, $table) { return new self( sprintf("An index with name '%s' was already defined on table '%s'.", $indexName, $table), self::INDEX_ALREADY_EXISTS ); } /** * @param string $columnName * @param string $table * * @return SchemaException */ public static function columnDoesNotExist($columnName, $table) { return new self( sprintf("There is no column with name '%s' on table '%s'.", $columnName, $table), self::COLUMN_DOESNT_EXIST ); } /** * @param string $namespaceName * * @return SchemaException */ public static function namespaceAlreadyExists($namespaceName) { return new self( sprintf("The namespace with name '%s' already exists.", $namespaceName), self::NAMESPACE_ALREADY_EXISTS ); } /** * @param string $tableName * * @return SchemaException */ public static function tableAlreadyExists($tableName) { return new self("The table with name '" . $tableName . "' already exists.", self::TABLE_ALREADY_EXISTS); } /** * @param string $tableName * @param string $columnName * * @return SchemaException */ public static function columnAlreadyExists($tableName, $columnName) { return new self( "The column '" . $columnName . "' on table '" . $tableName . "' already exists.", self::COLUMN_ALREADY_EXISTS ); } /** * @param string $name * * @return SchemaException */ public static function sequenceAlreadyExists($name) { return new self("The sequence '" . $name . "' already exists.", self::SEQUENCE_ALREADY_EXISTS); } /** * @param string $name * * @return SchemaException */ public static function sequenceDoesNotExist($name) { return new self("There exists no sequence with the name '" . $name . "'.", self::SEQUENCE_DOENST_EXIST); } /** * @param string $fkName * @param string $table * * @return SchemaException */ public static function foreignKeyDoesNotExist($fkName, $table) { return new self( sprintf("There exists no foreign key with the name '%s' on table '%s'.", $fkName, $table), self::FOREIGNKEY_DOESNT_EXIST ); } /** * @return SchemaException */ public static function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey) { return new self( 'The performed schema operation on ' . $localTable->getName() . ' requires a named foreign key, ' . 'but the given foreign key from (' . implode(', ', $foreignKey->getColumns()) . ') onto foreign table ' . "'" . $foreignKey->getForeignTableName() . "' (" . implode(', ', $foreignKey->getForeignColumns()) . ')' . ' is currently unnamed.' ); } /** * @param string $changeName * * @return SchemaException */ public static function alterTableChangeNotSupported($changeName) { return new self( sprintf("Alter table change not supported, given '%s'", $changeName) ); } } dbal/lib/Doctrine/DBAL/Schema/Sequence.php 0000644 00000005610 15120025742 0014146 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; use function count; use function sprintf; /** * Sequence structure. */ class Sequence extends AbstractAsset { /** @var int */ protected $allocationSize = 1; /** @var int */ protected $initialValue = 1; /** @var int|null */ protected $cache; /** * @param string $name * @param int $allocationSize * @param int $initialValue * @param int|null $cache */ public function __construct($name, $allocationSize = 1, $initialValue = 1, $cache = null) { $this->_setName($name); $this->setAllocationSize($allocationSize); $this->setInitialValue($initialValue); $this->cache = $cache; } /** * @return int */ public function getAllocationSize() { return $this->allocationSize; } /** * @return int */ public function getInitialValue() { return $this->initialValue; } /** * @return int|null */ public function getCache() { return $this->cache; } /** * @param int $allocationSize * * @return Sequence */ public function setAllocationSize($allocationSize) { $this->allocationSize = (int) $allocationSize ?: 1; return $this; } /** * @param int $initialValue * * @return Sequence */ public function setInitialValue($initialValue) { $this->initialValue = (int) $initialValue ?: 1; return $this; } /** * @param int $cache * * @return Sequence */ public function setCache($cache) { $this->cache = $cache; return $this; } /** * Checks if this sequence is an autoincrement sequence for a given table. * * This is used inside the comparator to not report sequences as missing, * when the "from" schema implicitly creates the sequences. * * @return bool */ public function isAutoIncrementsFor(Table $table) { $primaryKey = $table->getPrimaryKey(); if ($primaryKey === null) { return false; } $pkColumns = $primaryKey->getColumns(); if (count($pkColumns) !== 1) { return false; } $column = $table->getColumn($pkColumns[0]); if (! $column->getAutoincrement()) { return false; } $sequenceName = $this->getShortestName($table->getNamespaceName()); $tableName = $table->getShortestName($table->getNamespaceName()); $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName())); return $tableSequenceName === $sequenceName; } /** * @return void */ public function visit(Visitor $visitor) { $visitor->acceptSequence($this); } } dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php 0000644 00000040664 15120025742 0016263 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Types\StringType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Type; use function array_change_key_case; use function array_map; use function array_merge; use function array_reverse; use function explode; use function file_exists; use function preg_match; use function preg_match_all; use function preg_quote; use function preg_replace; use function rtrim; use function sprintf; use function str_replace; use function strpos; use function strtolower; use function trim; use function unlink; use function usort; use const CASE_LOWER; /** * Sqlite SchemaManager. */ class SqliteSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} */ public function dropDatabase($database) { if (! file_exists($database)) { return; } unlink($database); } /** * {@inheritdoc} */ public function createDatabase($database) { $params = $this->_conn->getParams(); $params['path'] = $database; unset($params['memory']); $conn = DriverManager::getConnection($params); $conn->connect(); $conn->close(); } /** * {@inheritdoc} */ public function renameTable($name, $newName) { $tableDiff = new TableDiff($name); $tableDiff->fromTable = $this->listTableDetails($name); $tableDiff->newName = $newName; $this->alterTable($tableDiff); } /** * {@inheritdoc} */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { $tableDiff = $this->getTableDiffForAlterForeignKey($table); $tableDiff->addedForeignKeys[] = $foreignKey; $this->alterTable($tableDiff); } /** * {@inheritdoc} */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { $tableDiff = $this->getTableDiffForAlterForeignKey($table); $tableDiff->changedForeignKeys[] = $foreignKey; $this->alterTable($tableDiff); } /** * {@inheritdoc} */ public function dropForeignKey($foreignKey, $table) { $tableDiff = $this->getTableDiffForAlterForeignKey($table); $tableDiff->removedForeignKeys[] = $foreignKey; $this->alterTable($tableDiff); } /** * {@inheritdoc} */ public function listTableForeignKeys($table, $database = null) { if ($database === null) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); $tableForeignKeys = $this->_conn->fetchAllAssociative($sql); if (! empty($tableForeignKeys)) { $createSql = $this->getCreateTableSQL($table); if ( $createSql !== null && preg_match_all( '# (?:CONSTRAINT\s+([^\s]+)\s+)? (?:FOREIGN\s+KEY[^\)]+\)\s*)? REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))? (?: [^,]*? (NOT\s+DEFERRABLE|DEFERRABLE) (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? )?#isx', $createSql, $match ) ) { $names = array_reverse($match[1]); $deferrable = array_reverse($match[2]); $deferred = array_reverse($match[3]); } else { $names = $deferrable = $deferred = []; } foreach ($tableForeignKeys as $key => $value) { $id = $value['id']; $tableForeignKeys[$key] = array_merge($tableForeignKeys[$key], [ 'constraint_name' => isset($names[$id]) && $names[$id] !== '' ? $names[$id] : $id, 'deferrable' => isset($deferrable[$id]) && strtolower($deferrable[$id]) === 'deferrable', 'deferred' => isset($deferred[$id]) && strtolower($deferred[$id]) === 'deferred', ]); } } return $this->_getPortableTableForeignKeysList($tableForeignKeys); } /** * {@inheritdoc} */ protected function _getPortableTableDefinition($table) { return $table['name']; } /** * {@inheritdoc} * * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html */ protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) { $indexBuffer = []; // fetch primary $indexArray = $this->_conn->fetchAllAssociative(sprintf( 'PRAGMA TABLE_INFO (%s)', $this->_conn->quote($tableName) )); usort( $indexArray, /** * @param array<string,mixed> $a * @param array<string,mixed> $b */ static function (array $a, array $b): int { if ($a['pk'] === $b['pk']) { return $a['cid'] - $b['cid']; } return $a['pk'] - $b['pk']; } ); foreach ($indexArray as $indexColumnRow) { if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') { continue; } $indexBuffer[] = [ 'key_name' => 'primary', 'primary' => true, 'non_unique' => false, 'column_name' => $indexColumnRow['name'], ]; } // fetch regular indexes foreach ($tableIndexes as $tableIndex) { // Ignore indexes with reserved names, e.g. autoindexes if (strpos($tableIndex['name'], 'sqlite_') === 0) { continue; } $keyName = $tableIndex['name']; $idx = []; $idx['key_name'] = $keyName; $idx['primary'] = false; $idx['non_unique'] = ! $tableIndex['unique']; $indexArray = $this->_conn->fetchAllAssociative(sprintf( 'PRAGMA INDEX_INFO (%s)', $this->_conn->quote($keyName) )); foreach ($indexArray as $indexColumnRow) { $idx['column_name'] = $indexColumnRow['name']; $indexBuffer[] = $idx; } } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } /** * @deprecated * * @param array<string, mixed> $tableIndex * * @return array<string, bool|string> */ protected function _getPortableTableIndexDefinition($tableIndex) { return [ 'name' => $tableIndex['name'], 'unique' => (bool) $tableIndex['unique'], ]; } /** * {@inheritdoc} */ protected function _getPortableTableColumnList($table, $database, $tableColumns) { $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); // find column with autoincrement $autoincrementColumn = null; $autoincrementCount = 0; foreach ($tableColumns as $tableColumn) { if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') { continue; } $autoincrementCount++; if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') { continue; } $autoincrementColumn = $tableColumn['name']; } if ($autoincrementCount === 1 && $autoincrementColumn !== null) { foreach ($list as $column) { if ($autoincrementColumn !== $column->getName()) { continue; } $column->setAutoincrement(true); } } // inspect column collation and comments $createSql = $this->getCreateTableSQL($table) ?? ''; foreach ($list as $columnName => $column) { $type = $column->getType(); if ($type instanceof StringType || $type instanceof TextType) { $column->setPlatformOption( 'collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY' ); } $comment = $this->parseColumnCommentFromSQL($columnName, $createSql); if ($comment === null) { continue; } $type = $this->extractDoctrineTypeFromComment($comment, ''); if ($type !== '') { $column->setType(Type::getType($type)); $comment = $this->removeDoctrineTypeFromComment($comment, $type); } $column->setComment($comment); } return $list; } /** * {@inheritdoc} */ protected function _getPortableTableColumnDefinition($tableColumn) { $parts = explode('(', $tableColumn['type']); $tableColumn['type'] = trim($parts[0]); if (isset($parts[1])) { $length = trim($parts[1], ')'); $tableColumn['length'] = $length; } $dbType = strtolower($tableColumn['type']); $length = $tableColumn['length'] ?? null; $unsigned = false; if (strpos($dbType, ' unsigned') !== false) { $dbType = str_replace(' unsigned', '', $dbType); $unsigned = true; } $fixed = false; $type = $this->_platform->getDoctrineTypeMapping($dbType); $default = $tableColumn['dflt_value']; if ($default === 'NULL') { $default = null; } if ($default !== null) { // SQLite returns the default value as a literal expression, so we need to parse it if (preg_match('/^\'(.*)\'$/s', $default, $matches)) { $default = str_replace("''", "'", $matches[1]); } } $notnull = (bool) $tableColumn['notnull']; if (! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; switch ($dbType) { case 'char': $fixed = true; break; case 'float': case 'double': case 'real': case 'decimal': case 'numeric': if (isset($tableColumn['length'])) { if (strpos($tableColumn['length'], ',') === false) { $tableColumn['length'] .= ',0'; } [$precision, $scale] = array_map('trim', explode(',', $tableColumn['length'])); } $length = null; break; } $options = [ 'length' => $length, 'unsigned' => $unsigned, 'fixed' => $fixed, 'notnull' => $notnull, 'default' => $default, 'precision' => $precision, 'scale' => $scale, 'autoincrement' => false, ]; return new Column($tableColumn['name'], Type::getType($type), $options); } /** * {@inheritdoc} */ protected function _getPortableViewDefinition($view) { return new View($view['name'], $view['sql']); } /** * {@inheritdoc} */ protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = []; foreach ($tableForeignKeys as $value) { $value = array_change_key_case($value, CASE_LOWER); $name = $value['constraint_name']; if (! isset($list[$name])) { if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') { $value['on_delete'] = null; } if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') { $value['on_update'] = null; } $list[$name] = [ 'name' => $name, 'local' => [], 'foreign' => [], 'foreignTable' => $value['table'], 'onDelete' => $value['on_delete'], 'onUpdate' => $value['on_update'], 'deferrable' => $value['deferrable'], 'deferred' => $value['deferred'], ]; } $list[$name]['local'][] = $value['from']; if ($value['to'] === null) { continue; } $list[$name]['foreign'][] = $value['to']; } $result = []; foreach ($list as $constraint) { $result[] = new ForeignKeyConstraint( $constraint['local'], $constraint['foreignTable'], $constraint['foreign'], $constraint['name'], [ 'onDelete' => $constraint['onDelete'], 'onUpdate' => $constraint['onUpdate'], 'deferrable' => $constraint['deferrable'], 'deferred' => $constraint['deferred'], ] ); } return $result; } /** * @param Table|string $table * * @return TableDiff * * @throws Exception */ private function getTableDiffForAlterForeignKey($table) { if (! $table instanceof Table) { $tableDetails = $this->tryMethod('listTableDetails', $table); if ($tableDetails === false) { throw new Exception( sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table) ); } $table = $tableDetails; } $tableDiff = new TableDiff($table->getName()); $tableDiff->fromTable = $table; return $tableDiff; } private function parseColumnCollationFromSQL(string $column, string $sql): ?string { $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } return $match[1]; } private function parseTableCommentFromSQL(string $table, string $sql): ?string { $pattern = '/\s* # Allow whitespace characters at start of line CREATE\sTABLE # Match "CREATE TABLE" (?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/') . '\W) # Match table name (quoted and unquoted) ( # Start capture (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s) )/ix'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); return $comment === '' ? null : $comment; } private function parseColumnCommentFromSQL(string $column, string $sql): ?string { $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i'; if (preg_match($pattern, $sql, $match) !== 1) { return null; } $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); return $comment === '' ? null : $comment; } private function getCreateTableSQL(string $table): ?string { return $this->_conn->fetchColumn( <<<'SQL' SELECT sql FROM ( SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master ) WHERE type = 'table' AND name = ? SQL , [$table] ) ?: null; } /** * @param string $name */ public function listTableDetails($name): Table { $table = parent::listTableDetails($name); $tableCreateSql = $this->getCreateTableSQL($name) ?? ''; $comment = $this->parseTableCommentFromSQL($name, $tableCreateSql); if ($comment !== null) { $table->addOption('comment', $comment); } return $table; } } dbal/lib/Doctrine/DBAL/Schema/Table.php 0000644 00000056703 15120025742 0013436 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\Types\Type; use function array_filter; use function array_merge; use function in_array; use function preg_match; use function strlen; use function strtolower; use const ARRAY_FILTER_USE_KEY; /** * Object Representation of a table. */ class Table extends AbstractAsset { /** @var Column[] */ protected $_columns = []; /** @var Index[] */ private $implicitIndexes = []; /** @var Index[] */ protected $_indexes = []; /** @var string|false */ protected $_primaryKeyName = false; /** @var ForeignKeyConstraint[] */ protected $_fkConstraints = []; /** @var mixed[] */ protected $_options = [ 'create_options' => [], ]; /** @var SchemaConfig|null */ protected $_schemaConfig; /** * @param string $name * @param Column[] $columns * @param Index[] $indexes * @param ForeignKeyConstraint[] $fkConstraints * @param int $idGeneratorType * @param mixed[] $options * * @throws Exception */ public function __construct( $name, array $columns = [], array $indexes = [], array $fkConstraints = [], $idGeneratorType = 0, array $options = [] ) { if (strlen($name) === 0) { throw Exception::invalidTableName($name); } $this->_setName($name); foreach ($columns as $column) { $this->_addColumn($column); } foreach ($indexes as $idx) { $this->_addIndex($idx); } foreach ($fkConstraints as $constraint) { $this->_addForeignKeyConstraint($constraint); } $this->_options = array_merge($this->_options, $options); } /** * @return void */ public function setSchemaConfig(SchemaConfig $schemaConfig) { $this->_schemaConfig = $schemaConfig; } /** * @return int */ protected function _getMaxIdentifierLength() { if ($this->_schemaConfig instanceof SchemaConfig) { return $this->_schemaConfig->getMaxIdentifierLength(); } return 63; } /** * Sets the Primary Key. * * @param string[] $columnNames * @param string|false $indexName * * @return self */ public function setPrimaryKey(array $columnNames, $indexName = false) { $this->_addIndex($this->_createIndex($columnNames, $indexName ?: 'primary', true, true)); foreach ($columnNames as $columnName) { $column = $this->getColumn($columnName); $column->setNotnull(true); } return $this; } /** * @param string[] $columnNames * @param string|null $indexName * @param string[] $flags * @param mixed[] $options * * @return self */ public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = []) { if ($indexName === null) { $indexName = $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'idx', $this->_getMaxIdentifierLength() ); } return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); } /** * Drops the primary key from this table. * * @return void */ public function dropPrimaryKey() { if ($this->_primaryKeyName === false) { return; } $this->dropIndex($this->_primaryKeyName); $this->_primaryKeyName = false; } /** * Drops an index from this table. * * @param string $name The index name. * * @return void * * @throws SchemaException If the index does not exist. */ public function dropIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } unset($this->_indexes[$name]); } /** * @param string[] $columnNames * @param string|null $indexName * @param mixed[] $options * * @return self */ public function addUniqueIndex(array $columnNames, $indexName = null, array $options = []) { if ($indexName === null) { $indexName = $this->_generateIdentifierName( array_merge([$this->getName()], $columnNames), 'uniq', $this->_getMaxIdentifierLength() ); } return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options)); } /** * Renames an index. * * @param string $oldName The name of the index to rename from. * @param string|null $newName The name of the index to rename to. * If null is given, the index name will be auto-generated. * * @return self This table instance. * * @throws SchemaException If no index exists for the given current name * or if an index with the given new name already exists on this table. */ public function renameIndex($oldName, $newName = null) { $oldName = $this->normalizeIdentifier($oldName); $normalizedNewName = $this->normalizeIdentifier($newName); if ($oldName === $normalizedNewName) { return $this; } if (! $this->hasIndex($oldName)) { throw SchemaException::indexDoesNotExist($oldName, $this->_name); } if ($this->hasIndex($normalizedNewName)) { throw SchemaException::indexAlreadyExists($normalizedNewName, $this->_name); } $oldIndex = $this->_indexes[$oldName]; if ($oldIndex->isPrimary()) { $this->dropPrimaryKey(); return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? false); } unset($this->_indexes[$oldName]); if ($oldIndex->isUnique()) { return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions()); } return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions()); } /** * Checks if an index begins in the order of the given columns. * * @param string[] $columnNames * * @return bool */ public function columnsAreIndexed(array $columnNames) { foreach ($this->getIndexes() as $index) { if ($index->spansColumns($columnNames)) { return true; } } return false; } /** * @param string[] $columnNames * @param string $indexName * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options * * @return Index * * @throws SchemaException */ private function _createIndex( array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [] ) { if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options); } /** * @param string $name * @param string $typeName * @param mixed[] $options * * @return Column */ public function addColumn($name, $typeName, array $options = []) { $column = new Column($name, Type::getType($typeName), $options); $this->_addColumn($column); return $column; } /** * Renames a Column. * * @deprecated * * @param string $oldName * @param string $name * * @return void * * @throws Exception */ public function renameColumn($oldName, $name) { throw new Exception('Table#renameColumn() was removed, because it drops and recreates ' . 'the column instead. There is no fix available, because a schema diff cannot reliably detect if a ' . 'column was renamed or one column was created and another one dropped.'); } /** * Change Column Details. * * @param string $name * @param mixed[] $options * * @return self */ public function changeColumn($name, array $options) { $column = $this->getColumn($name); $column->setOptions($options); return $this; } /** * Drops a Column from the Table. * * @param string $name * * @return self */ public function dropColumn($name) { $name = $this->normalizeIdentifier($name); unset($this->_columns[$name]); return $this; } /** * Adds a foreign key constraint. * * Name is inferred from the local columns. * * @param Table|string $foreignTable Table schema instance or table name * @param string[] $localColumnNames * @param string[] $foreignColumnNames * @param mixed[] $options * @param string|null $constraintName * * @return self */ public function addForeignKeyConstraint( $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null ) { $constraintName = $constraintName ?: $this->_generateIdentifierName( array_merge((array) $this->getName(), $localColumnNames), 'fk', $this->_getMaxIdentifierLength() ); return $this->addNamedForeignKeyConstraint( $constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options ); } /** * Adds a foreign key constraint. * * Name is to be generated by the database itself. * * @deprecated Use {@link addForeignKeyConstraint} * * @param Table|string $foreignTable Table schema instance or table name * @param string[] $localColumnNames * @param string[] $foreignColumnNames * @param mixed[] $options * * @return self */ public function addUnnamedForeignKeyConstraint( $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [] ) { return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options); } /** * Adds a foreign key constraint with a given name. * * @deprecated Use {@link addForeignKeyConstraint} * * @param string $name * @param Table|string $foreignTable Table schema instance or table name * @param string[] $localColumnNames * @param string[] $foreignColumnNames * @param mixed[] $options * * @return self * * @throws SchemaException */ public function addNamedForeignKeyConstraint( $name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [] ) { if ($foreignTable instanceof Table) { foreach ($foreignColumnNames as $columnName) { if (! $foreignTable->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); } } } foreach ($localColumnNames as $columnName) { if (! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } $constraint = new ForeignKeyConstraint( $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options ); $this->_addForeignKeyConstraint($constraint); return $this; } /** * @param string $name * @param mixed $value * * @return self */ public function addOption($name, $value) { $this->_options[$name] = $value; return $this; } /** * @return void * * @throws SchemaException */ protected function _addColumn(Column $column) { $columnName = $column->getName(); $columnName = $this->normalizeIdentifier($columnName); if (isset($this->_columns[$columnName])) { throw SchemaException::columnAlreadyExists($this->getName(), $columnName); } $this->_columns[$columnName] = $column; } /** * Adds an index to the table. * * @return self * * @throws SchemaException */ protected function _addIndex(Index $indexCandidate) { $indexName = $indexCandidate->getName(); $indexName = $this->normalizeIdentifier($indexName); $replacedImplicitIndexes = []; foreach ($this->implicitIndexes as $name => $implicitIndex) { if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) { continue; } $replacedImplicitIndexes[] = $name; } if ( (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || ($this->_primaryKeyName !== false && $indexCandidate->isPrimary()) ) { throw SchemaException::indexAlreadyExists($indexName, $this->_name); } foreach ($replacedImplicitIndexes as $name) { unset($this->_indexes[$name], $this->implicitIndexes[$name]); } if ($indexCandidate->isPrimary()) { $this->_primaryKeyName = $indexName; } $this->_indexes[$indexName] = $indexCandidate; return $this; } /** * @return void */ protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) { $constraint->setLocalTable($this); if (strlen($constraint->getName())) { $name = $constraint->getName(); } else { $name = $this->_generateIdentifierName( array_merge((array) $this->getName(), $constraint->getLocalColumns()), 'fk', $this->_getMaxIdentifierLength() ); } $name = $this->normalizeIdentifier($name); $this->_fkConstraints[$name] = $constraint; /* Add an implicit index (defined by the DBAL) on the foreign key columns. If there is already a user-defined index that fulfills these requirements drop the request. In the case of __construct() calling this method during hydration from schema-details, all the explicitly added indexes lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns). */ $indexName = $this->_generateIdentifierName( array_merge([$this->getName()], $constraint->getColumns()), 'idx', $this->_getMaxIdentifierLength() ); $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false); foreach ($this->_indexes as $existingIndex) { if ($indexCandidate->isFullfilledBy($existingIndex)) { return; } } $this->_addIndex($indexCandidate); $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; } /** * Returns whether this table has a foreign key constraint with the given name. * * @param string $name * * @return bool */ public function hasForeignKey($name) { $name = $this->normalizeIdentifier($name); return isset($this->_fkConstraints[$name]); } /** * Returns the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return ForeignKeyConstraint * * @throws SchemaException If the foreign key does not exist. */ public function getForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } return $this->_fkConstraints[$name]; } /** * Removes the foreign key constraint with the given name. * * @param string $name The constraint name. * * @return void * * @throws SchemaException */ public function removeForeignKey($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasForeignKey($name)) { throw SchemaException::foreignKeyDoesNotExist($name, $this->_name); } unset($this->_fkConstraints[$name]); } /** * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest) * * @return Column[] */ public function getColumns() { $primaryKey = $this->getPrimaryKey(); $primaryKeyColumns = []; if ($primaryKey !== null) { $primaryKeyColumns = $this->filterColumns($primaryKey->getColumns()); } return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns); } /** * Returns foreign key columns * * @return Column[] */ private function getForeignKeyColumns() { $foreignKeyColumns = []; foreach ($this->getForeignKeys() as $foreignKey) { $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns()); } return $this->filterColumns($foreignKeyColumns); } /** * Returns only columns that have specified names * * @param string[] $columnNames * * @return Column[] */ private function filterColumns(array $columnNames) { return array_filter($this->_columns, static function (string $columnName) use ($columnNames) { return in_array($columnName, $columnNames, true); }, ARRAY_FILTER_USE_KEY); } /** * Returns whether this table has a Column with the given name. * * @param string $name The column name. * * @return bool */ public function hasColumn($name) { $name = $this->normalizeIdentifier($name); return isset($this->_columns[$name]); } /** * Returns the Column with the given name. * * @param string $name The column name. * * @return Column * * @throws SchemaException If the column does not exist. */ public function getColumn($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasColumn($name)) { throw SchemaException::columnDoesNotExist($name, $this->_name); } return $this->_columns[$name]; } /** * Returns the primary key. * * @return Index|null The primary key, or null if this Table has no primary key. */ public function getPrimaryKey() { if ($this->_primaryKeyName !== false) { return $this->getIndex($this->_primaryKeyName); } return null; } /** * Returns the primary key columns. * * @return string[] * * @throws Exception */ public function getPrimaryKeyColumns() { $primaryKey = $this->getPrimaryKey(); if ($primaryKey === null) { throw new Exception('Table ' . $this->getName() . ' has no primary key.'); } return $primaryKey->getColumns(); } /** * Returns whether this table has a primary key. * * @return bool */ public function hasPrimaryKey() { return $this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName); } /** * Returns whether this table has an Index with the given name. * * @param string $name The index name. * * @return bool */ public function hasIndex($name) { $name = $this->normalizeIdentifier($name); return isset($this->_indexes[$name]); } /** * Returns the Index with the given name. * * @param string $name The index name. * * @return Index * * @throws SchemaException If the index does not exist. */ public function getIndex($name) { $name = $this->normalizeIdentifier($name); if (! $this->hasIndex($name)) { throw SchemaException::indexDoesNotExist($name, $this->_name); } return $this->_indexes[$name]; } /** * @return Index[] */ public function getIndexes() { return $this->_indexes; } /** * Returns the foreign key constraints. * * @return ForeignKeyConstraint[] */ public function getForeignKeys() { return $this->_fkConstraints; } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->_options[$name]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->_options[$name]; } /** * @return mixed[] */ public function getOptions() { return $this->_options; } /** * @return void */ public function visit(Visitor $visitor) { $visitor->acceptTable($this); foreach ($this->getColumns() as $column) { $visitor->acceptColumn($this, $column); } foreach ($this->getIndexes() as $index) { $visitor->acceptIndex($this, $index); } foreach ($this->getForeignKeys() as $constraint) { $visitor->acceptForeignKey($this, $constraint); } } /** * Clone of a Table triggers a deep clone of all affected assets. * * @return void */ public function __clone() { foreach ($this->_columns as $k => $column) { $this->_columns[$k] = clone $column; } foreach ($this->_indexes as $k => $index) { $this->_indexes[$k] = clone $index; } foreach ($this->_fkConstraints as $k => $fk) { $this->_fkConstraints[$k] = clone $fk; $this->_fkConstraints[$k]->setLocalTable($this); } } /** * Normalizes a given identifier. * * Trims quotes and lowercases the given identifier. * * @param string|null $identifier The identifier to normalize. * * @return string The normalized identifier. */ private function normalizeIdentifier($identifier) { if ($identifier === null) { return ''; } return $this->trimQuotes(strtolower($identifier)); } public function setComment(?string $comment): self { // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options. $this->addOption('comment', $comment); return $this; } public function getComment(): ?string { return $this->_options['comment'] ?? null; } } dbal/lib/Doctrine/DBAL/Schema/TableDiff.php 0000644 00000006161 15120025742 0014220 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Table Diff. */ class TableDiff { /** @var string */ public $name; /** @var string|false */ public $newName = false; /** * All added columns * * @var Column[] */ public $addedColumns; /** * All changed columns * * @var ColumnDiff[] */ public $changedColumns = []; /** * All removed columns * * @var Column[] */ public $removedColumns = []; /** * Columns that are only renamed from key to column instance name. * * @var Column[] */ public $renamedColumns = []; /** * All added indexes. * * @var Index[] */ public $addedIndexes = []; /** * All changed indexes. * * @var Index[] */ public $changedIndexes = []; /** * All removed indexes * * @var Index[] */ public $removedIndexes = []; /** * Indexes that are only renamed but are identical otherwise. * * @var Index[] */ public $renamedIndexes = []; /** * All added foreign key definitions * * @var ForeignKeyConstraint[] */ public $addedForeignKeys = []; /** * All changed foreign keys * * @var ForeignKeyConstraint[] */ public $changedForeignKeys = []; /** * All removed foreign keys * * @var ForeignKeyConstraint[]|string[] */ public $removedForeignKeys = []; /** @var Table|null */ public $fromTable; /** * Constructs an TableDiff object. * * @param string $tableName * @param Column[] $addedColumns * @param ColumnDiff[] $changedColumns * @param Column[] $removedColumns * @param Index[] $addedIndexes * @param Index[] $changedIndexes * @param Index[] $removedIndexes */ public function __construct( $tableName, $addedColumns = [], $changedColumns = [], $removedColumns = [], $addedIndexes = [], $changedIndexes = [], $removedIndexes = [], ?Table $fromTable = null ) { $this->name = $tableName; $this->addedColumns = $addedColumns; $this->changedColumns = $changedColumns; $this->removedColumns = $removedColumns; $this->addedIndexes = $addedIndexes; $this->changedIndexes = $changedIndexes; $this->removedIndexes = $removedIndexes; $this->fromTable = $fromTable; } /** * @param AbstractPlatform $platform The platform to use for retrieving this table diff's name. * * @return Identifier */ public function getName(AbstractPlatform $platform) { return new Identifier( $this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name ); } /** * @return Identifier|false */ public function getNewName() { if ($this->newName === false) { return false; } return new Identifier($this->newName); } } dbal/lib/Doctrine/DBAL/Schema/View.php 0000644 00000000711 15120025742 0013305 0 ustar 00 <?php namespace Doctrine\DBAL\Schema; /** * Representation of a Database View. */ class View extends AbstractAsset { /** @var string */ private $sql; /** * @param string $name * @param string $sql */ public function __construct($name, $sql) { $this->_setName($name); $this->sql = $sql; } /** * @return string */ public function getSql() { return $this->sql; } } dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php 0000644 00000010311 15120025742 0021421 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding\SQLAzure\Schema; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Visitor\Visitor; use RuntimeException; use function in_array; /** * Converts a single tenant schema into a multi-tenant schema for SQL Azure * Federations under the following assumptions: * * - Every table is part of the multi-tenant application, only explicitly * excluded tables are non-federated. The behavior of the tables being in * global or federated database is undefined. It depends on you selecting a * federation before DDL statements or not. * - Every Primary key of a federated table is extended by another column * 'tenant_id' with a default value of the SQLAzure function * `federation_filtering_value('tenant_id')`. * - You always have to work with `filtering=On` when using federations with this * multi-tenant approach. * - Primary keys are either using globally unique ids (GUID, Table Generator) * or you explicitly add the tenant_id in every UPDATE or DELETE statement * (otherwise they will affect the same-id rows from other tenants as well). * SQLAzure throws errors when you try to create IDENTIY columns on federated * tables. * * @deprecated */ class MultiTenantVisitor implements Visitor { /** @var string[] */ private $excludedTables = []; /** @var string */ private $tenantColumnName; /** @var string */ private $tenantColumnType = 'integer'; /** * Name of the federation distribution, defaulting to the tenantColumnName * if not specified. * * @var string */ private $distributionName; /** * @param string[] $excludedTables * @param string $tenantColumnName * @param string|null $distributionName */ public function __construct(array $excludedTables = [], $tenantColumnName = 'tenant_id', $distributionName = null) { $this->excludedTables = $excludedTables; $this->tenantColumnName = $tenantColumnName; $this->distributionName = $distributionName ?: $tenantColumnName; } /** * {@inheritdoc} */ public function acceptTable(Table $table) { if (in_array($table->getName(), $this->excludedTables)) { return; } $table->addColumn($this->tenantColumnName, $this->tenantColumnType, [ 'default' => "federation_filtering_value('" . $this->distributionName . "')", ]); $clusteredIndex = $this->getClusteredIndex($table); $indexColumns = $clusteredIndex->getColumns(); $indexColumns[] = $this->tenantColumnName; if ($clusteredIndex->isPrimary()) { $table->dropPrimaryKey(); $table->setPrimaryKey($indexColumns); } else { $table->dropIndex($clusteredIndex->getName()); $table->addIndex($indexColumns, $clusteredIndex->getName()); $table->getIndex($clusteredIndex->getName())->addFlag('clustered'); } } /** * @param Table $table * * @return Index * * @throws RuntimeException */ private function getClusteredIndex($table) { foreach ($table->getIndexes() as $index) { if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) { return $index; } if ($index->hasFlag('clustered')) { return $index; } } throw new RuntimeException('No clustered index found on table ' . $table->getName()); } /** * {@inheritdoc} */ public function acceptSchema(Schema $schema) { } /** * {@inheritdoc} */ public function acceptColumn(Table $table, Column $column) { } /** * {@inheritdoc} */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { } /** * {@inheritdoc} */ public function acceptIndex(Table $table, Index $index) { } /** * {@inheritdoc} */ public function acceptSequence(Sequence $sequence) { } } dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php 0000644 00000021245 15120025742 0022655 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding\SQLAzure; use Closure; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer; use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer; use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use RuntimeException; use function array_merge; /** * SQL Azure Schema Synchronizer. * * Will iterate over all shards when performing schema operations. This is done * by partitioning the passed schema into subschemas for the federation and the * global database and then applying the operations step by step using the * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}. * * @deprecated */ class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer { public const FEDERATION_TABLE_FEDERATED = 'azure.federated'; public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName'; /** @var SQLAzureShardManager */ private $shardManager; /** @var SchemaSynchronizer */ private $synchronizer; public function __construct(Connection $conn, SQLAzureShardManager $shardManager, ?SchemaSynchronizer $sync = null) { parent::__construct($conn); $this->shardManager = $shardManager; $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn); } /** * {@inheritdoc} */ public function getCreateSchema(Schema $createSchema) { $sql = []; [$global, $federation] = $this->partitionSchema($createSchema); $globalSql = $this->synchronizer->getCreateSchema($global); if ($globalSql) { $sql[] = "-- Create Root Federation\n" . 'USE FEDERATION ROOT WITH RESET;'; $sql = array_merge($sql, $globalSql); } $federationSql = $this->synchronizer->getCreateSchema($federation); if ($federationSql) { $defaultValue = $this->getFederationTypeDefaultValue(); $sql[] = $this->getCreateFederationStatement(); $sql[] = 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $defaultValue . ')' . ' WITH RESET, FILTERING = OFF;'; $sql = array_merge($sql, $federationSql); } return $sql; } /** * {@inheritdoc} */ public function getUpdateSchema(Schema $toSchema, $noDrops = false) { return $this->work($toSchema, static function ($synchronizer, $schema) use ($noDrops) { return $synchronizer->getUpdateSchema($schema, $noDrops); }); } /** * {@inheritdoc} */ public function getDropSchema(Schema $dropSchema) { return $this->work($dropSchema, static function ($synchronizer, $schema) { return $synchronizer->getDropSchema($schema); }); } /** * {@inheritdoc} */ public function createSchema(Schema $createSchema) { $this->processSql($this->getCreateSchema($createSchema)); } /** * {@inheritdoc} */ public function updateSchema(Schema $toSchema, $noDrops = false) { $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); } /** * {@inheritdoc} */ public function dropSchema(Schema $dropSchema) { $this->processSqlSafely($this->getDropSchema($dropSchema)); } /** * {@inheritdoc} */ public function getDropAllSchema() { $this->shardManager->selectGlobal(); $globalSql = $this->synchronizer->getDropAllSchema(); $sql = []; if ($globalSql) { $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; $sql = array_merge($sql, $globalSql); } $shards = $this->shardManager->getShards(); foreach ($shards as $shard) { $this->shardManager->selectShard($shard['rangeLow']); $federationSql = $this->synchronizer->getDropAllSchema(); if (! $federationSql) { continue; } $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" . 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ')' . ' WITH RESET, FILTERING = OFF;'; $sql = array_merge($sql, $federationSql); } $sql[] = 'USE FEDERATION ROOT WITH RESET;'; $sql[] = 'DROP FEDERATION ' . $this->shardManager->getFederationName(); return $sql; } /** * {@inheritdoc} */ public function dropAllSchema() { $this->processSqlSafely($this->getDropAllSchema()); } /** * @return Schema[] */ private function partitionSchema(Schema $schema) { return [ $this->extractSchemaFederation($schema, false), $this->extractSchemaFederation($schema, true), ]; } /** * @param bool $isFederation * * @return Schema * * @throws RuntimeException */ private function extractSchemaFederation(Schema $schema, $isFederation) { $partitionedSchema = clone $schema; foreach ($partitionedSchema->getTables() as $table) { if ($isFederation) { $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey()); } if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { $partitionedSchema->dropTable($table->getName()); } else { foreach ($table->getForeignKeys() as $fk) { $foreignTable = $schema->getTable($fk->getForeignTableName()); if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { throw new RuntimeException('Cannot have foreign key between global/federation.'); } } } } return $partitionedSchema; } /** * Work on the Global/Federation based on currently existing shards and * perform the given operation on the underlying schema synchronizer given * the different partitioned schema instances. * * @return string[] */ private function work(Schema $schema, Closure $operation) { [$global, $federation] = $this->partitionSchema($schema); $sql = []; $this->shardManager->selectGlobal(); $globalSql = $operation($this->synchronizer, $global); if ($globalSql) { $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; $sql = array_merge($sql, $globalSql); } $shards = $this->shardManager->getShards(); foreach ($shards as $shard) { $this->shardManager->selectShard($shard['rangeLow']); $federationSql = $operation($this->synchronizer, $federation); if (! $federationSql) { continue; } $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" . 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ')' . ' WITH RESET, FILTERING = OFF;'; $sql = array_merge($sql, $federationSql); } return $sql; } /** * @return string */ private function getFederationTypeDefaultValue() { $federationType = Type::getType($this->shardManager->getDistributionType()); switch ($federationType->getName()) { case Types::GUID: $defaultValue = '00000000-0000-0000-0000-000000000000'; break; case Types::INTEGER: case Types::SMALLINT: case Types::BIGINT: $defaultValue = '0'; break; default: $defaultValue = ''; break; } return $defaultValue; } /** * @return string */ private function getCreateFederationStatement() { $federationType = Type::getType($this->shardManager->getDistributionType()); $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform()); return "--Create Federation\n" . 'CREATE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' ' . $federationTypeSql . ' RANGE)'; } } dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php 0000644 00000013251 15120025742 0020326 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding\SQLAzure; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Sharding\ShardingException; use Doctrine\DBAL\Sharding\ShardManager; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use RuntimeException; use function sprintf; /** * Sharding using the SQL Azure Federations support. * * @deprecated */ class SQLAzureShardManager implements ShardManager { /** @var string */ private $federationName; /** @var bool */ private $filteringEnabled; /** @var string */ private $distributionKey; /** @var string */ private $distributionType; /** @var Connection */ private $conn; /** @var string|null */ private $currentDistributionValue; /** * @throws ShardingException */ public function __construct(Connection $conn) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3595', 'Native Sharding support in DBAL is removed without replacement.' ); $this->conn = $conn; $params = $conn->getParams(); if (! isset($params['sharding']['federationName'])) { throw ShardingException::missingDefaultFederationName(); } if (! isset($params['sharding']['distributionKey'])) { throw ShardingException::missingDefaultDistributionKey(); } if (! isset($params['sharding']['distributionType'])) { throw ShardingException::missingDistributionType(); } $this->federationName = $params['sharding']['federationName']; $this->distributionKey = $params['sharding']['distributionKey']; $this->distributionType = $params['sharding']['distributionType']; $this->filteringEnabled = (bool) ($params['sharding']['filteringEnabled'] ?? false); } /** * Gets the name of the federation. * * @return string */ public function getFederationName() { return $this->federationName; } /** * Gets the distribution key. * * @return string */ public function getDistributionKey() { return $this->distributionKey; } /** * Gets the Doctrine Type name used for the distribution. * * @return string */ public function getDistributionType() { return $this->distributionType; } /** * Sets Enabled/Disable filtering on the fly. * * @param bool $flag * * @return void */ public function setFilteringEnabled($flag) { $this->filteringEnabled = (bool) $flag; } /** * {@inheritDoc} */ public function selectGlobal() { if ($this->conn->isTransactionActive()) { throw ShardingException::activeTransaction(); } $sql = 'USE FEDERATION ROOT WITH RESET'; $this->conn->exec($sql); $this->currentDistributionValue = null; } /** * {@inheritDoc} */ public function selectShard($distributionValue) { if ($this->conn->isTransactionActive()) { throw ShardingException::activeTransaction(); } $platform = $this->conn->getDatabasePlatform(); $sql = sprintf( 'USE FEDERATION %s (%s = %s) WITH RESET, FILTERING = %s;', $platform->quoteIdentifier($this->federationName), $platform->quoteIdentifier($this->distributionKey), $this->conn->quote($distributionValue), ($this->filteringEnabled ? 'ON' : 'OFF') ); $this->conn->exec($sql); $this->currentDistributionValue = $distributionValue; } /** * {@inheritDoc} */ public function getCurrentDistributionValue() { return $this->currentDistributionValue; } /** * {@inheritDoc} */ public function getShards() { $sql = 'SELECT member_id as id, distribution_name as distribution_key, CAST(range_low AS CHAR) AS rangeLow, CAST(range_high AS CHAR) AS rangeHigh FROM sys.federation_member_distributions d INNER JOIN sys.federations f ON f.federation_id = d.federation_id WHERE f.name = ' . $this->conn->quote($this->federationName); return $this->conn->fetchAllAssociative($sql); } /** * {@inheritDoc} */ public function queryAll($sql, array $params = [], array $types = []) { $shards = $this->getShards(); if (! $shards) { throw new RuntimeException('No shards found for ' . $this->federationName); } $result = []; $oldDistribution = $this->getCurrentDistributionValue(); foreach ($shards as $shard) { $this->selectShard($shard['rangeLow']); foreach ($this->conn->fetchAllAssociative($sql, $params, $types) as $row) { $result[] = $row; } } if ($oldDistribution === null) { $this->selectGlobal(); } else { $this->selectShard($oldDistribution); } return $result; } /** * Splits Federation at a given distribution value. * * @param mixed $splitDistributionValue * * @return void */ public function splitFederation($splitDistributionValue) { $type = Type::getType($this->distributionType); $sql = 'ALTER FEDERATION ' . $this->getFederationName() . ' ' . 'SPLIT AT (' . $this->getDistributionKey() . ' = ' . $this->conn->quote($splitDistributionValue, $type->getBindingType()) . ')'; $this->conn->exec($sql); } } dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php 0000644 00000000706 15120025742 0021535 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding\ShardChoser; use Doctrine\DBAL\Sharding\PoolingShardConnection; /** * The MultiTenant Shard choser assumes that the distribution value directly * maps to the shard id. * * @deprecated */ class MultiTenantShardChoser implements ShardChoser { /** * {@inheritdoc} */ public function pickShard($distributionValue, PoolingShardConnection $conn) { return $distributionValue; } } dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php 0000644 00000001031 15120025742 0017340 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding\ShardChoser; use Doctrine\DBAL\Sharding\PoolingShardConnection; /** * Given a distribution value this shard-choser strategy will pick the shard to * connect to for retrieving rows with the distribution value. * * @deprecated */ interface ShardChoser { /** * Picks a shard for the given distribution value. * * @param string|int $distributionValue * * @return string|int */ public function pickShard($distributionValue, PoolingShardConnection $conn); } dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php 0000644 00000016275 15120025742 0017357 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; use InvalidArgumentException; use function array_merge; use function is_numeric; use function is_string; /** * Sharding implementation that pools many different connections * internally and serves data from the currently active connection. * * The internals of this class are: * * - All sharding clients are specified and given a shard-id during * configuration. * - By default, the global shard is selected. If no global shard is configured * an exception is thrown on access. * - Selecting a shard by distribution value delegates the mapping * "distributionValue" => "client" to the ShardChoser interface. * - An exception is thrown if trying to switch shards during an open * transaction. * * Instantiation through the DriverManager looks like: * * @deprecated * * @example * * $conn = DriverManager::getConnection(array( * 'wrapperClass' => 'Doctrine\DBAL\Sharding\PoolingShardConnection', * 'driver' => 'pdo_mysql', * 'global' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), * 'shards' => array( * array('id' => 1, 'user' => 'slave1', 'password', 'host' => '', 'dbname' => ''), * array('id' => 2, 'user' => 'slave2', 'password', 'host' => '', 'dbname' => ''), * ), * 'shardChoser' => 'Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser', * )); * $shardManager = $conn->getShardManager(); * $shardManager->selectGlobal(); * $shardManager->selectShard($value); */ class PoolingShardConnection extends Connection { /** @var DriverConnection[] */ private $activeConnections = []; /** @var string|int|null */ private $activeShardId; /** @var mixed[] */ private $connectionParameters = []; /** * {@inheritDoc} * * @internal The connection can be only instantiated by the driver manager. * * @throws InvalidArgumentException */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { if (! isset($params['global'], $params['shards'])) { throw new InvalidArgumentException("Connection Parameters require 'global' and 'shards' configurations."); } if (! isset($params['shardChoser'])) { throw new InvalidArgumentException("Missing Shard Choser configuration 'shardChoser'"); } if (is_string($params['shardChoser'])) { $params['shardChoser'] = new $params['shardChoser'](); } if (! ($params['shardChoser'] instanceof ShardChoser)) { throw new InvalidArgumentException( "The 'shardChoser' configuration is not a valid instance of " . ShardChoser::class ); } $this->connectionParameters[0] = array_merge($params, $params['global']); foreach ($params['shards'] as $shard) { if (! isset($shard['id'])) { throw new InvalidArgumentException( "Missing 'id' for one configured shard. Please specify a unique shard-id." ); } if (! is_numeric($shard['id']) || $shard['id'] < 1) { throw new InvalidArgumentException('Shard Id has to be a non-negative number.'); } if (isset($this->connectionParameters[$shard['id']])) { throw new InvalidArgumentException('Shard ' . $shard['id'] . ' is duplicated in the configuration.'); } $this->connectionParameters[$shard['id']] = array_merge($params, $shard); } parent::__construct($params, $driver, $config, $eventManager); } /** * Get active shard id. * * @return string|int|null */ public function getActiveShardId() { return $this->activeShardId; } /** * {@inheritdoc} */ public function getParams() { return $this->activeShardId ? $this->connectionParameters[$this->activeShardId] : $this->connectionParameters[0]; } /** * {@inheritdoc} */ public function getHost() { $params = $this->getParams(); return $params['host'] ?? parent::getHost(); } /** * {@inheritdoc} */ public function getPort() { $params = $this->getParams(); return $params['port'] ?? parent::getPort(); } /** * {@inheritdoc} */ public function getUsername() { $params = $this->getParams(); return $params['user'] ?? parent::getUsername(); } /** * {@inheritdoc} */ public function getPassword() { $params = $this->getParams(); return $params['password'] ?? parent::getPassword(); } /** * Connects to a given shard. * * @param string|int|null $shardId * * @return bool * * @throws ShardingException */ public function connect($shardId = null) { if ($shardId === null && $this->_conn) { return false; } if ($shardId !== null && $shardId === $this->activeShardId) { return false; } if ($this->getTransactionNestingLevel() > 0) { throw new ShardingException('Cannot switch shard when transaction is active.'); } $activeShardId = $this->activeShardId = (int) $shardId; if (isset($this->activeConnections[$activeShardId])) { $this->_conn = $this->activeConnections[$activeShardId]; return false; } $this->_conn = $this->activeConnections[$activeShardId] = $this->connectTo($activeShardId); if ($this->_eventManager->hasListeners(Events::postConnect)) { $eventArgs = new ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Connects to a specific connection. * * @param string|int $shardId * * @return \Doctrine\DBAL\Driver\Connection */ protected function connectTo($shardId) { $params = $this->getParams(); $driverOptions = $params['driverOptions'] ?? []; $connectionParams = $this->connectionParameters[$shardId]; $user = $connectionParams['user'] ?? null; $password = $connectionParams['password'] ?? null; return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); } /** * @param string|int|null $shardId * * @return bool */ public function isConnected($shardId = null) { if ($shardId === null) { return $this->_conn !== null; } return isset($this->activeConnections[$shardId]); } /** * @return void */ public function close() { $this->_conn = null; $this->activeConnections = []; $this->activeShardId = null; } } dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php 0000644 00000004765 15120025742 0016633 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding; use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; use Doctrine\Deprecations\Deprecation; use RuntimeException; /** * Shard Manager for the Connection Pooling Shard Strategy * * @deprecated */ class PoolingShardManager implements ShardManager { /** @var PoolingShardConnection */ private $conn; /** @var ShardChoser */ private $choser; /** @var string|null */ private $currentDistributionValue; public function __construct(PoolingShardConnection $conn) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3595', 'Native Sharding support in DBAL is removed without replacement.' ); $params = $conn->getParams(); $this->conn = $conn; $this->choser = $params['shardChoser']; } /** * {@inheritDoc} */ public function selectGlobal() { $this->conn->connect(0); $this->currentDistributionValue = null; } /** * {@inheritDoc} */ public function selectShard($distributionValue) { $shardId = $this->choser->pickShard($distributionValue, $this->conn); $this->conn->connect($shardId); $this->currentDistributionValue = $distributionValue; } /** * {@inheritDoc} */ public function getCurrentDistributionValue() { return $this->currentDistributionValue; } /** * {@inheritDoc} */ public function getShards() { $params = $this->conn->getParams(); $shards = []; foreach ($params['shards'] as $shard) { $shards[] = ['id' => $shard['id']]; } return $shards; } /** * {@inheritDoc} * * @throws RuntimeException */ public function queryAll($sql, array $params, array $types) { $shards = $this->getShards(); if (! $shards) { throw new RuntimeException('No shards found.'); } $result = []; $oldDistribution = $this->getCurrentDistributionValue(); foreach ($shards as $shard) { $this->conn->connect($shard['id']); foreach ($this->conn->fetchAllAssociative($sql, $params, $types) as $row) { $result[] = $row; } } if ($oldDistribution === null) { $this->selectGlobal(); } else { $this->selectShard($oldDistribution); } return $result; } } dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php 0000644 00000004374 15120025742 0015277 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding; /** * Sharding Manager gives access to APIs to implementing sharding on top of * Doctrine\DBAL\Connection instances. * * For simplicity and developer ease-of-use (and understanding) the sharding * API only covers single shard queries, no fan-out support. It is primarily * suited for multi-tenant applications. * * The assumption about sharding here * is that a distribution value can be found that gives access to all the * necessary data for all use-cases. Switching between shards should be done with * caution, especially if lazy loading is implemented. Any query is always * executed against the last shard that was selected. If a query is created for * a shard Y but then a shard X is selected when its actually executed you * will hit the wrong shard. * * @deprecated */ interface ShardManager { /** * Selects global database with global data. * * This is the default database that is connected when no shard is * selected. * * @return void */ public function selectGlobal(); /** * Selects the shard against which the queries after this statement will be issued. * * @param string $distributionValue * * @return void * * @throws ShardingException If no value is passed as shard identifier. */ public function selectShard($distributionValue); /** * Gets the distribution value currently used for sharding. * * @return string|null */ public function getCurrentDistributionValue(); /** * Gets information about the amount of shards and other details. * * Format is implementation specific, each shard is one element and has an * 'id' attribute at least. * * @return mixed[][] */ public function getShards(); /** * Queries all shards in undefined order and return the results appended to * each other. Restore the previous distribution value after execution. * * Using {@link Connection::fetchAll()} to retrieve rows internally. * * @param string $sql * @param mixed[] $params * @param int[]|string[] $types * * @return mixed[] */ public function queryAll($sql, array $params, array $types); } dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php 0000644 00000003003 15120025742 0016345 0 ustar 00 <?php namespace Doctrine\DBAL\Sharding; use Doctrine\DBAL\Exception; /** * Sharding related Exceptions * * @deprecated * * @psalm-immutable */ class ShardingException extends Exception { /** * @return ShardingException */ public static function notImplemented() { return new self('This functionality is not implemented with this sharding provider.', 1331557937); } /** * @return ShardingException */ public static function missingDefaultFederationName() { return new self('SQLAzure requires a federation name to be set during sharding configuration.', 1332141280); } /** * @return ShardingException */ public static function missingDefaultDistributionKey() { return new self('SQLAzure requires a distribution key to be set during sharding configuration.', 1332141329); } /** * @return ShardingException */ public static function activeTransaction() { return new self('Cannot switch shard during an active transaction.', 1332141766); } /** * @return ShardingException */ public static function noShardDistributionValue() { return new self('You have to specify a string or integer as shard distribution value.', 1332142103); } /** * @return ShardingException */ public static function missingDistributionType() { return new self("You have to specify a sharding distribution type such as 'integer', 'string', 'guid'."); } } dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php 0000644 00000010136 15120025742 0020026 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement; use InvalidArgumentException; use PDOException; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function assert; use function error_get_last; use function file_exists; use function file_get_contents; use function is_readable; use function realpath; use function sprintf; use const PHP_EOL; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. * * @deprecated Use a database client application instead */ class ImportCommand extends Command { /** @return void */ protected function configure() { $this ->setName('dbal:import') ->setDescription('Import SQL file(s) directly to Database.') ->setDefinition([new InputArgument( 'file', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'File path(s) of SQL to be executed.' ), ]) ->setHelp(<<<EOT Import SQL file(s) directly to Database. EOT ); } /** * {@inheritdoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $conn = $this->getHelper('db')->getConnection(); $fileNames = $input->getArgument('file'); if ($fileNames === null) { return 0; } foreach ((array) $fileNames as $fileName) { $filePath = realpath($fileName); // Phar compatibility. if ($filePath === false) { $filePath = $fileName; } if (! file_exists($filePath)) { throw new InvalidArgumentException( sprintf("SQL file '<info>%s</info>' does not exist.", $filePath) ); } if (! is_readable($filePath)) { throw new InvalidArgumentException( sprintf("SQL file '<info>%s</info>' does not have read permissions.", $filePath) ); } $output->write(sprintf("Processing file '<info>%s</info>'... ", $filePath)); $sql = @file_get_contents($filePath); if ($sql === false) { $message = sprintf("Unable to read SQL file '<info>%s</info>'", $filePath); $error = error_get_last(); if ($error !== null) { $message .= ': ' . $error['message']; } throw new RuntimeException($message); } if ($conn instanceof PDOConnection) { // PDO Drivers try { $lines = 0; $stmt = $conn->prepare($sql); assert($stmt instanceof PDOStatement); $stmt->execute(); do { // Required due to "MySQL has gone away!" issue $stmt->fetch(); $stmt->closeCursor(); $lines++; } while ($stmt->nextRowset()); $output->write(sprintf('%d statements executed!', $lines) . PHP_EOL); } catch (PDOException $e) { $output->write('error!' . PHP_EOL); throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } } else { // Non-PDO Drivers (ie. OCI8 driver) $stmt = $conn->prepare($sql); $rs = $stmt->execute(); if (! $rs) { $error = $stmt->errorInfo(); $output->write('error!' . PHP_EOL); throw new RuntimeException($error[2], $error[0]); } $output->writeln('OK!' . PHP_EOL); $stmt->closeCursor(); } } return 0; } } dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php 0000644 00000017540 15120025742 0021360 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\Keywords\DB2Keywords; use Doctrine\DBAL\Platforms\Keywords\KeywordList; use Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords; use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords; use Doctrine\DBAL\Platforms\Keywords\OracleKeywords; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords; use Doctrine\DBAL\Platforms\Keywords\PostgreSQL92Keywords; use Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords; use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator; use Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords; use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords; use Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords; use Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\Deprecations\Deprecation; use Exception; use InvalidArgumentException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_keys; use function assert; use function count; use function implode; use function is_array; use function is_string; class ReservedWordsCommand extends Command { /** @var array<string,class-string<KeywordList>> */ private $keywordListClasses = [ 'mysql' => MySQLKeywords::class, 'mysql57' => MySQL57Keywords::class, 'mysql80' => MySQL80Keywords::class, 'sqlserver' => SQLServerKeywords::class, 'sqlserver2005' => SQLServer2005Keywords::class, 'sqlserver2008' => SQLServer2008Keywords::class, 'sqlserver2012' => SQLServer2012Keywords::class, 'sqlite' => SQLiteKeywords::class, 'pgsql' => PostgreSQLKeywords::class, 'pgsql91' => PostgreSQL91Keywords::class, 'pgsql92' => PostgreSQL92Keywords::class, 'oracle' => OracleKeywords::class, 'db2' => DB2Keywords::class, 'sqlanywhere' => SQLAnywhereKeywords::class, 'sqlanywhere11' => SQLAnywhere11Keywords::class, 'sqlanywhere12' => SQLAnywhere12Keywords::class, 'sqlanywhere16' => SQLAnywhere16Keywords::class, ]; /** @var ConnectionProvider|null */ private $connectionProvider; public function __construct(?ConnectionProvider $connectionProvider = null) { parent::__construct(); $this->connectionProvider = $connectionProvider; if ($connectionProvider !== null) { return; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3956', 'Not passing a connection provider as the first constructor argument is deprecated' ); } /** * If you want to add or replace a keywords list use this command. * * @param string $name * @param class-string<KeywordList> $class * * @return void */ public function setKeywordListClass($name, $class) { $this->keywordListClasses[$name] = $class; } /** @return void */ protected function configure() { $this ->setName('dbal:reserved-words') ->setDescription('Checks if the current database contains identifiers that are reserved.') ->setDefinition([ new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), new InputOption( 'list', 'l', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Keyword-List name.' ), ]) ->setHelp(<<<EOT Checks if the current database contains tables and columns with names that are identifiers in this dialect or in other SQL dialects. By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server, Oracle and SQL Anywhere keywords are checked: <info>%command.full_name%</info> If you want to check against specific dialects you can pass them to the command: <info>%command.full_name% -l mysql -l pgsql</info> The following keyword lists are currently shipped with Doctrine: * mysql * mysql57 * mysql80 * pgsql * pgsql92 * sqlite * oracle * sqlserver * sqlserver2005 * sqlserver2008 * sqlserver2012 * sqlanywhere * sqlanywhere11 * sqlanywhere12 * sqlanywhere16 * db2 (Not checked by default) EOT ); } /** * {@inheritdoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $conn = $this->getConnection($input); $keywordLists = $input->getOption('list'); if (is_string($keywordLists)) { $keywordLists = [$keywordLists]; } elseif (! is_array($keywordLists)) { $keywordLists = []; } if (! $keywordLists) { $keywordLists = [ 'mysql', 'mysql57', 'mysql80', 'pgsql', 'pgsql92', 'sqlite', 'oracle', 'sqlserver', 'sqlserver2005', 'sqlserver2008', 'sqlserver2012', 'sqlanywhere', 'sqlanywhere11', 'sqlanywhere12', 'sqlanywhere16', ]; } $keywords = []; foreach ($keywordLists as $keywordList) { if (! isset($this->keywordListClasses[$keywordList])) { throw new InvalidArgumentException( "There exists no keyword list with name '" . $keywordList . "'. " . 'Known lists: ' . implode(', ', array_keys($this->keywordListClasses)) ); } $class = $this->keywordListClasses[$keywordList]; $keywords[] = new $class(); } $output->write( 'Checking keyword violations for <comment>' . implode(', ', $keywordLists) . '</comment>...', true ); $schema = $conn->getSchemaManager()->createSchema(); $visitor = new ReservedKeywordsValidator($keywords); $schema->visit($visitor); $violations = $visitor->getViolations(); if (count($violations) !== 0) { $output->write( 'There are <error>' . count($violations) . '</error> reserved keyword violations' . ' in your database schema:', true ); foreach ($violations as $violation) { $output->write(' - ' . $violation, true); } return 1; } $output->write('No reserved keywords violations have been found!', true); return 0; } private function getConnection(InputInterface $input): Connection { $connectionName = $input->getOption('connection'); assert(is_string($connectionName) || $connectionName === null); if ($this->connectionProvider === null) { if ($connectionName !== null) { throw new Exception('Specifying a connection is only supported when a ConnectionProvider is used.'); } return $this->getHelper('db')->getConnection(); } if ($connectionName !== null) { return $this->connectionProvider->getConnection($connectionName); } return $this->connectionProvider->getDefaultConnection(); } } dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php 0000644 00000007326 15120025742 0020007 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\DBAL\Tools\Dumper; use Doctrine\Deprecations\Deprecation; use Exception; use LogicException; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function assert; use function is_numeric; use function is_string; use function stripos; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. */ class RunSqlCommand extends Command { /** @var ConnectionProvider|null */ private $connectionProvider; public function __construct(?ConnectionProvider $connectionProvider = null) { parent::__construct(); $this->connectionProvider = $connectionProvider; if ($connectionProvider !== null) { return; } Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3956', 'Not passing a connection provider as the first constructor argument is deprecated' ); } /** @return void */ protected function configure() { $this ->setName('dbal:run-sql') ->setDescription('Executes arbitrary SQL directly from the command line.') ->setDefinition([ new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'), new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set.', '7'), new InputOption('force-fetch', null, InputOption::VALUE_NONE, 'Forces fetching the result.'), ]) ->setHelp(<<<EOT The <info>%command.name%</info> command executes the given SQL query and outputs the results: <info>php %command.full_name% "SELECT * FROM users"</info> EOT ); } /** * {@inheritdoc} * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) { $conn = $this->getConnection($input); $sql = $input->getArgument('sql'); if ($sql === null) { throw new RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); } assert(is_string($sql)); $depth = $input->getOption('depth'); if (! is_numeric($depth)) { throw new LogicException("Option 'depth' must contains an integer value"); } if (stripos($sql, 'select') === 0 || $input->getOption('force-fetch')) { $resultSet = $conn->fetchAllAssociative($sql); } else { $resultSet = $conn->executeStatement($sql); } $output->write(Dumper::dump($resultSet, (int) $depth)); return 0; } private function getConnection(InputInterface $input): Connection { $connectionName = $input->getOption('connection'); assert(is_string($connectionName) || $connectionName === null); if ($this->connectionProvider === null) { if ($connectionName !== null) { throw new Exception('Specifying a connection is only supported when a ConnectionProvider is used.'); } return $this->getHelper('db')->getConnection(); } if ($connectionName !== null) { return $this->connectionProvider->getConnection($connectionName); } return $this->connectionProvider->getDefaultConnection(); } } dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionProvider/SingleConnectionProvider.php 0000644 00000001755 15120025742 0024474 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionNotFound; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use function sprintf; class SingleConnectionProvider implements ConnectionProvider { /** @var Connection */ private $connection; /** @var string */ private $defaultConnectionName; public function __construct(Connection $connection, string $defaultConnectionName = 'default') { $this->connection = $connection; $this->defaultConnectionName = $defaultConnectionName; } public function getDefaultConnection(): Connection { return $this->connection; } public function getConnection(string $name): Connection { if ($name !== $this->defaultConnectionName) { throw new ConnectionNotFound(sprintf('Connection with name "%s" does not exist.', $name)); } return $this->connection; } } dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php 0000644 00000001551 15120025742 0020356 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console\Helper; use Doctrine\DBAL\Connection; use Symfony\Component\Console\Helper\Helper; /** * Doctrine CLI Connection Helper. * * @deprecated use a ConnectionProvider instead. */ class ConnectionHelper extends Helper { /** * The Doctrine database Connection. * * @var Connection */ protected $_connection; /** * @param Connection $connection The Doctrine database Connection. */ public function __construct(Connection $connection) { $this->_connection = $connection; } /** * Retrieves the Doctrine database Connection. * * @return Connection */ public function getConnection() { return $this->_connection; } /** * {@inheritdoc} */ public function getName() { return 'connection'; } } dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionNotFound.php 0000644 00000000212 15120025742 0017445 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use OutOfBoundsException; final class ConnectionNotFound extends OutOfBoundsException { } dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionProvider.php 0000644 00000000520 15120025742 0017505 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Doctrine\DBAL\Connection; interface ConnectionProvider { public function getDefaultConnection(): Connection; /** * @throws ConnectionNotFound in case a connection with the given name does not exist. */ public function getConnection(string $name): Connection; } dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php 0000644 00000007326 15120025742 0016502 0 ustar 00 <?php namespace Doctrine\DBAL\Tools\Console; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\Command\ImportCommand; use Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand; use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; use Doctrine\DBAL\Version; use Doctrine\Deprecations\Deprecation; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\HelperSet; use TypeError; use function sprintf; /** * Handles running the Console Tools inside Symfony Console context. */ class ConsoleRunner { /** * Create a Symfony Console HelperSet * * @deprecated use a ConnectionProvider instead. * * @return HelperSet */ public static function createHelperSet(Connection $connection) { return new HelperSet([ 'db' => new ConnectionHelper($connection), ]); } /** * Runs console with the given connection provider or helperset (deprecated). * * @param ConnectionProvider|HelperSet $helperSetOrConnectionProvider * @param Command[] $commands * * @return void */ public static function run($helperSetOrConnectionProvider, $commands = []) { $cli = new Application('Doctrine Command Line Interface', Version::VERSION); $cli->setCatchExceptions(true); $connectionProvider = null; if ($helperSetOrConnectionProvider instanceof HelperSet) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3956', 'Passing an instance of "%s" as the first argument is deprecated. Pass an instance of "%s" instead.', HelperSet::class, ConnectionProvider::class ); $connectionProvider = null; $cli->setHelperSet($helperSetOrConnectionProvider); } elseif ($helperSetOrConnectionProvider instanceof ConnectionProvider) { $connectionProvider = $helperSetOrConnectionProvider; } else { throw new TypeError(sprintf( 'First argument must be an instance of "%s" or "%s"', HelperSet::class, ConnectionProvider::class )); } self::addCommands($cli, $connectionProvider); $cli->addCommands($commands); $cli->run(); } /** * @return void */ public static function addCommands(Application $cli, ?ConnectionProvider $connectionProvider = null) { $cli->addCommands([ new RunSqlCommand($connectionProvider), new ImportCommand(), new ReservedWordsCommand($connectionProvider), ]); } /** * Prints the instructions to create a configuration file * * @return void */ public static function printCliConfigTemplate() { echo <<<'HELP' You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine-DBAL Console working. You can use the following sample as a template: <?php use Doctrine\DBAL\Tools\Console\ConnectionProvider\SingleConnectionProvider; // You can append new commands to $commands array, if needed // replace with the mechanism to retrieve DBAL connection(s) in your app // and return a Doctrine\DBAL\Tools\Console\ConnectionProvider instance. $connection = getDBALConnection(); // in case you have a single connection you can use SingleConnectionProvider // otherwise you need to implement the Doctrine\DBAL\Tools\Console\ConnectionProvider interface with your custom logic return new SingleConnectionProvider($connection); HELP; } } dbal/lib/Doctrine/DBAL/Tools/Dumper.php 0000644 00000010666 15120025742 0013541 0 ustar 00 <?php namespace Doctrine\DBAL\Tools; use ArrayIterator; use ArrayObject; use DateTimeInterface; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Persistence\Proxy; use stdClass; use function array_keys; use function assert; use function class_exists; use function count; use function end; use function explode; use function extension_loaded; use function get_class; use function html_entity_decode; use function ini_set; use function is_array; use function is_object; use function is_string; use function ob_get_clean; use function ob_start; use function strip_tags; use function strlen; use function strrpos; use function substr; use function var_dump; /** * Static class used to dump the variable to be used on output. * Simplified port of Util\Debug from doctrine/common. * * @internal */ final class Dumper { /** * Private constructor (prevents instantiation). * * @codeCoverageIgnore */ private function __construct() { } /** * Returns a dump of the public, protected and private properties of $var. * * @link https://xdebug.org/ * * @param mixed $var The variable to dump. * @param int $maxDepth The maximum nesting level for object properties. */ public static function dump($var, int $maxDepth = 2): string { $html = ini_set('html_errors', '1'); assert(is_string($html)); if (extension_loaded('xdebug')) { ini_set('xdebug.var_display_max_depth', (string) $maxDepth); } $var = self::export($var, $maxDepth); ob_start(); var_dump($var); try { $output = ob_get_clean(); assert(is_string($output)); return strip_tags(html_entity_decode($output)); } finally { ini_set('html_errors', $html); } } /** * @param mixed $var * * @return mixed */ public static function export($var, int $maxDepth) { $return = null; $isObj = is_object($var); if ($var instanceof Collection) { $var = $var->toArray(); } if ($maxDepth === 0) { return is_object($var) ? get_class($var) : (is_array($var) ? 'Array(' . count($var) . ')' : $var); } if (is_array($var)) { $return = []; foreach ($var as $k => $v) { $return[$k] = self::export($v, $maxDepth - 1); } return $return; } if (! $isObj) { return $var; } $return = new stdClass(); if ($var instanceof DateTimeInterface) { $return->__CLASS__ = get_class($var); $return->date = $var->format('c'); $return->timezone = $var->getTimezone()->getName(); return $return; } $return->__CLASS__ = self::getClass($var); if ($var instanceof Proxy) { $return->__IS_PROXY__ = true; $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); } if ($var instanceof ArrayObject || $var instanceof ArrayIterator) { $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); } return self::fillReturnWithClassAttributes($var, $return, $maxDepth); } /** * Fill the $return variable with class attributes * Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075} * * @param object $var * * @return mixed */ private static function fillReturnWithClassAttributes($var, stdClass $return, int $maxDepth) { $clone = (array) $var; foreach (array_keys($clone) as $key) { $aux = explode("\0", $key); $name = end($aux); if ($aux[0] === '') { $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private'); } $return->$name = self::export($clone[$key], $maxDepth - 1); } return $return; } /** * @param object $object */ private static function getClass($object): string { $class = get_class($object); if (! class_exists(Proxy::class)) { return $class; } $pos = strrpos($class, '\\' . Proxy::MARKER . '\\'); if ($pos === false) { return $class; } return substr($class, $pos + strlen(Proxy::MARKER) + 2); } } dbal/lib/Doctrine/DBAL/Types/ArrayType.php 0000644 00000003114 15120025742 0014217 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; /** * Type that maps a PHP array to a clob SQL type. */ class ArrayType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { // @todo 3.0 - $value === null check to save real NULL in database return serialize($value); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritdoc} */ public function getName() { return Types::ARRAY; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/AsciiStringType.php 0000644 00000001136 15120025742 0015362 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; final class AsciiStringType extends StringType { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getAsciiStringTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::ASCII; } public function getName(): string { return Types::ASCII_STRING; } } dbal/lib/Doctrine/DBAL/Types/BigIntType.php 0000644 00000001531 15120025742 0014316 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database BIGINT to a PHP string. */ class BigIntType extends Type implements PhpIntegerMappingType { /** * {@inheritdoc} */ public function getName() { return Types::BIGINT; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBigIntTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::STRING; } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (string) $value; } } dbal/lib/Doctrine/DBAL/Types/BinaryType.php 0000644 00000002503 15120025742 0014366 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps ab SQL BINARY/VARBINARY to a PHP resource stream. */ class BinaryType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBinaryTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BINARY); } return $value; } /** * {@inheritdoc} */ public function getName() { return Types::BINARY; } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::BINARY; } } dbal/lib/Doctrine/DBAL/Types/BlobType.php 0000644 00000002465 15120025742 0014027 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use function assert; use function fopen; use function fseek; use function fwrite; use function is_resource; use function is_string; /** * Type that maps an SQL BLOB to a PHP resource stream. */ class BlobType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBlobTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if (is_string($value)) { $fp = fopen('php://temp', 'rb+'); assert(is_resource($fp)); fwrite($fp, $value); fseek($fp, 0); $value = $fp; } if (! is_resource($value)) { throw ConversionException::conversionFailed($value, Types::BLOB); } return $value; } /** * {@inheritdoc} */ public function getName() { return Types::BLOB; } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::LARGE_OBJECT; } } dbal/lib/Doctrine/DBAL/Types/BooleanType.php 0000644 00000001772 15120025742 0014530 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL boolean to a PHP boolean. */ class BooleanType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getBooleanTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $platform->convertBooleansToDatabaseValue($value); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $platform->convertFromBoolean($value); } /** * {@inheritdoc} */ public function getName() { return Types::BOOLEAN; } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::BOOLEAN; } } dbal/lib/Doctrine/DBAL/Types/ConversionException.php 0000644 00000006766 15120025742 0016323 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Throwable; use function get_class; use function gettype; use function implode; use function is_object; use function is_scalar; use function sprintf; use function strlen; use function substr; /** * Conversion Exception is thrown when the database to PHP conversion fails. * * @psalm-immutable */ class ConversionException extends Exception { /** * Thrown when a Database to Doctrine Type Conversion fails. * * @param string $value * @param string $toType * * @return ConversionException */ public static function conversionFailed($value, $toType, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType, 0, $previous); } /** * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement * about the expected format. * * @param string $value * @param string $toType * @param string $expectedFormat * * @return ConversionException */ public static function conversionFailedFormat($value, $toType, $expectedFormat, ?Throwable $previous = null) { $value = strlen($value) > 32 ? substr($value, 0, 20) . '...' : $value; return new self( 'Could not convert database value "' . $value . '" to Doctrine Type ' . $toType . '. Expected format: ' . $expectedFormat, 0, $previous ); } /** * Thrown when the PHP value passed to the converter was not of the expected type. * * @param mixed $value * @param string $toType * @param string[] $possibleTypes * * @return ConversionException */ public static function conversionFailedInvalidType( $value, $toType, array $possibleTypes, ?Throwable $previous = null ) { $actualType = is_object($value) ? get_class($value) : gettype($value); if (is_scalar($value)) { return new self(sprintf( "Could not convert PHP value '%s' of type '%s' to type '%s'. Expected one of the following types: %s", $value, $actualType, $toType, implode(', ', $possibleTypes) ), 0, $previous); } return new self(sprintf( "Could not convert PHP value of type '%s' to type '%s'. Expected one of the following types: %s", $actualType, $toType, implode(', ', $possibleTypes) ), 0, $previous); } /** * @param mixed $value * @param string $format * @param string $error * * @return ConversionException */ public static function conversionFailedSerialization($value, $format, $error) { $actualType = is_object($value) ? get_class($value) : gettype($value); return new self(sprintf( "Could not convert PHP type '%s' to '%s', as an '%s' error was triggered by the serialization", $actualType, $format, $error )); } public static function conversionFailedUnserialization(string $format, string $error): self { return new self(sprintf( "Could not convert database value to '%s' as an error was triggered by the unserialization: '%s'", $format, $error )); } } dbal/lib/Doctrine/DBAL/Types/DateImmutableType.php 0000644 00000003013 15120025742 0015654 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Immutable type of {@see DateType}. */ class DateImmutableType extends DateType { /** * {@inheritdoc} */ public function getName() { return Types::DATE_IMMUTABLE; } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class] ); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getDateFormatString(), $value); if (! $dateTime) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString() ); } return $dateTime; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/DateIntervalType.php 0000644 00000003677 15120025742 0015541 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateInterval; use Doctrine\DBAL\Platforms\AbstractPlatform; use Throwable; use function substr; /** * Type that maps interval string to a PHP DateInterval Object. */ class DateIntervalType extends Type { public const FORMAT = '%RP%YY%MM%DDT%HH%IM%SS'; /** * {@inheritdoc} */ public function getName() { return Types::DATEINTERVAL; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { $column['length'] = 255; return $platform->getVarcharTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } if ($value instanceof DateInterval) { return $value->format(self::FORMAT); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateInterval) { return $value; } $negative = false; if (isset($value[0]) && ($value[0] === '+' || $value[0] === '-')) { $negative = $value[0] === '-'; $value = substr($value, 1); } try { $interval = new DateInterval($value); if ($negative) { $interval->invert = 1; } return $interval; } catch (Throwable $exception) { throw ConversionException::conversionFailedFormat($value, $this->getName(), self::FORMAT, $exception); } } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/DateTimeImmutableType.php 0000644 00000003243 15120025742 0016500 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use function date_create_immutable; /** * Immutable type of {@see DateTimeType}. */ class DateTimeImmutableType extends DateTimeType { /** * {@inheritdoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class] ); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeFormatString(), $value); if (! $dateTime) { $dateTime = date_create_immutable($value); } if (! $dateTime) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString() ); } return $dateTime; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/DateTimeType.php 0000644 00000003241 15120025742 0014636 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use function date_create; /** * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. */ class DateTimeType extends Type implements PhpDateTimeMappingType { /** * {@inheritdoc} */ public function getName() { return Types::DATETIME_MUTABLE; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTimeTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeInterface) { return $value; } $val = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); if (! $val) { $val = date_create($value); } if (! $val) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeFormatString() ); } return $val; } } dbal/lib/Doctrine/DBAL/Types/DateTimeTzImmutableType.php 0000644 00000003057 15120025742 0017021 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Immutable type of {@see DateTimeTzType}. */ class DateTimeTzImmutableType extends DateTimeTzType { /** * {@inheritdoc} */ public function getName() { return Types::DATETIMETZ_IMMUTABLE; } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeTzFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class] ); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat($platform->getDateTimeTzFormatString(), $value); if (! $dateTime) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeTzFormatString() ); } return $dateTime; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php 0000644 00000004571 15120025742 0015163 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * DateTime type saving additional timezone information. * * Caution: Databases are not necessarily experts at storing timezone related * data of dates. First, of all the supported vendors only PostgreSQL and Oracle * support storing Timezone data. But those two don't save the actual timezone * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") * but the current offset of them related to UTC. That means depending on daylight saving times * or not you may get different offsets. * * This datatype makes only sense to use, if your application works with an offset, not * with an actual timezone that uses transitions. Otherwise your DateTime instance * attached with a timezone such as Europe/Berlin gets saved into the database with * the offset and re-created from persistence with only the offset, not the original timezone * attached. */ class DateTimeTzType extends Type implements PhpDateTimeMappingType { /** * {@inheritdoc} */ public function getName() { return Types::DATETIMETZ_MUTABLE; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTimeTzTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateTimeTzFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeInterface) { return $value; } $val = DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); if (! $val) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateTimeTzFormatString() ); } return $val; } } dbal/lib/Doctrine/DBAL/Types/DateType.php 0000644 00000002766 15120025742 0014032 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL DATE to a PHP Date object. */ class DateType extends Type { /** * {@inheritdoc} */ public function getName() { return Types::DATE_MUTABLE; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDateTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeInterface) { return $value->format($platform->getDateFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeInterface) { return $value; } $val = DateTime::createFromFormat('!' . $platform->getDateFormatString(), $value); if (! $val) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getDateFormatString() ); } return $val; } } dbal/lib/Doctrine/DBAL/Types/DecimalType.php 0000644 00000001720 15120025742 0014500 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_float; use function is_int; use const PHP_VERSION_ID; /** * Type that maps an SQL DECIMAL to a PHP string. */ class DecimalType extends Type { /** * {@inheritdoc} */ public function getName() { return Types::DECIMAL; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getDecimalTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { // Some drivers starting from PHP 8.1 can represent decimals as float/int // See also: https://github.com/doctrine/dbal/pull/4818 if (PHP_VERSION_ID >= 80100 && (is_float($value) || is_int($value))) { return (string) $value; } return $value; } } dbal/lib/Doctrine/DBAL/Types/FloatType.php 0000644 00000001125 15120025742 0014206 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; class FloatType extends Type { /** * {@inheritdoc} */ public function getName() { return Types::FLOAT; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getFloatDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (float) $value; } } dbal/lib/Doctrine/DBAL/Types/GuidType.php 0000644 00000001253 15120025742 0014033 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Represents a GUID/UUID datatype (both are actually synonyms) in the database. */ class GuidType extends StringType { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getGuidTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getName() { return Types::GUID; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return ! $platform->hasNativeGuidType(); } } dbal/lib/Doctrine/DBAL/Types/IntegerType.php 0000644 00000001524 15120025742 0014541 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL INT to a PHP integer. */ class IntegerType extends Type implements PhpIntegerMappingType { /** * {@inheritdoc} */ public function getName() { return Types::INTEGER; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getIntegerTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::INTEGER; } } dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php 0000644 00000001603 15120025742 0015052 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function json_decode; use function stream_get_contents; /** * Array Type which can be used to generate json arrays. * * @deprecated Use JsonType instead */ class JsonArrayType extends JsonType { /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value === '') { return []; } $value = is_resource($value) ? stream_get_contents($value) : $value; return json_decode($value, true); } /** * {@inheritdoc} */ public function getName() { return Types::JSON_ARRAY; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/JsonType.php 0000644 00000003376 15120025742 0014064 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function json_decode; use function json_encode; use function json_last_error; use function json_last_error_msg; use function stream_get_contents; use const JSON_ERROR_NONE; /** * Type generating json objects values */ class JsonType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getJsonTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $encoded = json_encode($value); if (json_last_error() !== JSON_ERROR_NONE) { throw ConversionException::conversionFailedSerialization($value, 'json', json_last_error_msg()); } return $encoded; } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value === '') { return null; } if (is_resource($value)) { $value = stream_get_contents($value); } $val = json_decode($value, true); if (json_last_error() !== JSON_ERROR_NONE) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } /** * {@inheritdoc} */ public function getName() { return Types::JSON; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return ! $platform->hasNativeJsonType(); } } dbal/lib/Doctrine/DBAL/Types/ObjectType.php 0000644 00000003004 15120025742 0014345 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function restore_error_handler; use function serialize; use function set_error_handler; use function stream_get_contents; use function unserialize; /** * Type that maps a PHP object to a clob SQL type. */ class ObjectType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return serialize($value); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $value = is_resource($value) ? stream_get_contents($value) : $value; set_error_handler(function (int $code, string $message): bool { throw ConversionException::conversionFailedUnserialization($this->getName(), $message); }); try { return unserialize($value); } finally { restore_error_handler(); } } /** * {@inheritdoc} */ public function getName() { return Types::OBJECT; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/PhpDateTimeMappingType.php 0000644 00000000267 15120025742 0016627 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP DateTimeInterface instance. * * @internal */ interface PhpDateTimeMappingType { } dbal/lib/Doctrine/DBAL/Types/PhpIntegerMappingType.php 0000644 00000000243 15120025742 0016522 0 ustar 00 <?php namespace Doctrine\DBAL\Types; /** * Implementations should map a database type to a PHP integer. * * @internal */ interface PhpIntegerMappingType { } dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php 0000644 00000002505 15120025742 0015374 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function explode; use function implode; use function is_resource; use function stream_get_contents; /** * Array Type which can be used for simple values. * * Only use this type if you are sure that your values cannot contain a ",". */ class SimpleArrayType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if (! $value) { return null; } return implode(',', $value); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return []; } $value = is_resource($value) ? stream_get_contents($value) : $value; return explode(',', $value); } /** * {@inheritdoc} */ public function getName() { return Types::SIMPLE_ARRAY; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/SmallIntType.php 0000644 00000001540 15120025742 0014665 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database SMALLINT to a PHP integer. */ class SmallIntType extends Type implements PhpIntegerMappingType { /** * {@inheritdoc} */ public function getName() { return Types::SMALLINT; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getSmallIntTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value === null ? null : (int) $value; } /** * {@inheritdoc} */ public function getBindingType() { return ParameterType::INTEGER; } } dbal/lib/Doctrine/DBAL/Types/StringType.php 0000644 00000001213 15120025742 0014405 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL VARCHAR to a PHP string. */ class StringType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getVarcharTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function getDefaultLength(AbstractPlatform $platform) { return $platform->getVarcharDefaultLength(); } /** * {@inheritdoc} */ public function getName() { return Types::STRING; } } dbal/lib/Doctrine/DBAL/Types/TextType.php 0000644 00000001335 15120025742 0014070 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; use function is_resource; use function stream_get_contents; /** * Type that maps an SQL CLOB to a PHP string. */ class TextType extends Type { /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { return is_resource($value) ? stream_get_contents($value) : $value; } /** * {@inheritdoc} */ public function getName() { return Types::TEXT; } } dbal/lib/Doctrine/DBAL/Types/TimeImmutableType.php 0000644 00000003013 15120025742 0015675 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Immutable type of {@see TimeType}. */ class TimeImmutableType extends TimeType { /** * {@inheritdoc} */ public function getName() { return Types::TIME_IMMUTABLE; } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class] ); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = DateTimeImmutable::createFromFormat('!' . $platform->getTimeFormatString(), $value); if (! $dateTime) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString() ); } return $dateTime; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/TimeType.php 0000644 00000002772 15120025742 0014050 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL TIME to a PHP DateTime object. */ class TimeType extends Type { /** * {@inheritdoc} */ public function getName() { return Types::TIME_MUTABLE; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $column, AbstractPlatform $platform) { return $platform->getTimeTypeDeclarationSQL($column); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeInterface) { return $value->format($platform->getTimeFormatString()); } throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeInterface) { return $value; } $val = DateTime::createFromFormat('!' . $platform->getTimeFormatString(), $value); if (! $val) { throw ConversionException::conversionFailedFormat( $value, $this->getName(), $platform->getTimeFormatString() ); } return $val; } } dbal/lib/Doctrine/DBAL/Types/Type.php 0000644 00000027500 15120025742 0013225 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Deprecations\Deprecation; use function array_map; use function get_class; use function str_replace; use function strrpos; use function substr; /** * The base class for so-called Doctrine mapping types. * * A Type object is obtained by calling the static {@link getType()} method. */ abstract class Type { /** @deprecated Use {@see Types::BIGINT} instead. */ public const BIGINT = Types::BIGINT; /** @deprecated Use {@see Types::BINARY} instead. */ public const BINARY = Types::BINARY; /** @deprecated Use {@see Types::BLOB} instead. */ public const BLOB = Types::BLOB; /** @deprecated Use {@see Types::BOOLEAN} instead. */ public const BOOLEAN = Types::BOOLEAN; /** @deprecated Use {@see Types::DATE_MUTABLE} instead. */ public const DATE = Types::DATE_MUTABLE; /** @deprecated Use {@see Types::DATE_IMMUTABLE} instead. */ public const DATE_IMMUTABLE = Types::DATE_IMMUTABLE; /** @deprecated Use {@see Types::DATEINTERVAL} instead. */ public const DATEINTERVAL = Types::DATEINTERVAL; /** @deprecated Use {@see Types::DATETIME_MUTABLE} instead. */ public const DATETIME = Types::DATETIME_MUTABLE; /** @deprecated Use {@see Types::DATETIME_IMMUTABLE} instead. */ public const DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE; /** @deprecated Use {@see Types::DATETIMETZ_MUTABLE} instead. */ public const DATETIMETZ = Types::DATETIMETZ_MUTABLE; /** @deprecated Use {@see Types::DATETIMETZ_IMMUTABLE} instead. */ public const DATETIMETZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE; /** @deprecated Use {@see Types::DECIMAL} instead. */ public const DECIMAL = Types::DECIMAL; /** @deprecated Use {@see Types::FLOAT} instead. */ public const FLOAT = Types::FLOAT; /** @deprecated Use {@see Types::GUID} instead. */ public const GUID = Types::GUID; /** @deprecated Use {@see Types::INTEGER} instead. */ public const INTEGER = Types::INTEGER; /** @deprecated Use {@see Types::JSON} instead. */ public const JSON = Types::JSON; /** @deprecated Use {@see Types::JSON_ARRAY} instead. */ public const JSON_ARRAY = Types::JSON_ARRAY; /** @deprecated Use {@see Types::OBJECT} instead. */ public const OBJECT = Types::OBJECT; /** @deprecated Use {@see Types::SIMPLE_ARRAY} instead. */ public const SIMPLE_ARRAY = Types::SIMPLE_ARRAY; /** @deprecated Use {@see Types::SMALLINT} instead. */ public const SMALLINT = Types::SMALLINT; /** @deprecated Use {@see Types::STRING} instead. */ public const STRING = Types::STRING; /** @deprecated Use {@see Types::ARRAY} instead. */ public const TARRAY = Types::ARRAY; /** @deprecated Use {@see Types::TEXT} instead. */ public const TEXT = Types::TEXT; /** @deprecated Use {@see Types::TIME_MUTABLE} instead. */ public const TIME = Types::TIME_MUTABLE; /** @deprecated Use {@see Types::TIME_IMMUTABLE} instead. */ public const TIME_IMMUTABLE = Types::TIME_IMMUTABLE; /** * The map of supported doctrine mapping types. */ private const BUILTIN_TYPES_MAP = [ Types::ARRAY => ArrayType::class, Types::ASCII_STRING => AsciiStringType::class, Types::BIGINT => BigIntType::class, Types::BINARY => BinaryType::class, Types::BLOB => BlobType::class, Types::BOOLEAN => BooleanType::class, Types::DATE_MUTABLE => DateType::class, Types::DATE_IMMUTABLE => DateImmutableType::class, Types::DATEINTERVAL => DateIntervalType::class, Types::DATETIME_MUTABLE => DateTimeType::class, Types::DATETIME_IMMUTABLE => DateTimeImmutableType::class, Types::DATETIMETZ_MUTABLE => DateTimeTzType::class, Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class, Types::DECIMAL => DecimalType::class, Types::FLOAT => FloatType::class, Types::GUID => GuidType::class, Types::INTEGER => IntegerType::class, Types::JSON => JsonType::class, Types::JSON_ARRAY => JsonArrayType::class, Types::OBJECT => ObjectType::class, Types::SIMPLE_ARRAY => SimpleArrayType::class, Types::SMALLINT => SmallIntType::class, Types::STRING => StringType::class, Types::TEXT => TextType::class, Types::TIME_MUTABLE => TimeType::class, Types::TIME_IMMUTABLE => TimeImmutableType::class, ]; /** @var TypeRegistry|null */ private static $typeRegistry; /** * @internal Do not instantiate directly - use {@see Type::addType()} method instead. */ final public function __construct() { } /** * Converts a value from its PHP representation to its database representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The database representation of the value. */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $value; } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * * @return mixed The PHP representation of the value. */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value; } /** * Gets the default length of this type. * * @deprecated Rely on information provided by the platform instead. * * @return int|null */ public function getDefaultLength(AbstractPlatform $platform) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3255', 'Type::getDefaultLength() is deprecated, use AbstractPlatform directly.' ); return null; } /** * Gets the SQL declaration snippet for a column of this type. * * @param mixed[] $column The column definition * @param AbstractPlatform $platform The currently used database platform. * * @return string */ abstract public function getSQLDeclaration(array $column, AbstractPlatform $platform); /** * Gets the name of this type. * * @return string * * @todo Needed? */ abstract public function getName(); final public static function getTypeRegistry(): TypeRegistry { if (self::$typeRegistry === null) { self::$typeRegistry = self::createTypeRegistry(); } return self::$typeRegistry; } private static function createTypeRegistry(): TypeRegistry { $instances = []; foreach (self::BUILTIN_TYPES_MAP as $name => $class) { $instances[$name] = new $class(); } return new TypeRegistry($instances); } /** * Factory method to create type instances. * Type instances are implemented as flyweights. * * @param string $name The name of the type (as returned by getName()). * * @return Type * * @throws Exception */ public static function getType($name) { return self::getTypeRegistry()->get($name); } /** * Adds a custom type to the type map. * * @param string $name The name of the type. This should correspond to what getName() returns. * @param class-string<Type> $className The class name of the custom type. * * @return void * * @throws Exception */ public static function addType($name, $className) { self::getTypeRegistry()->register($name, new $className()); } /** * Checks if exists support for a type. * * @param string $name The name of the type. * * @return bool TRUE if type is supported; FALSE otherwise. */ public static function hasType($name) { return self::getTypeRegistry()->has($name); } /** * Overrides an already defined type to use a different implementation. * * @param string $name * @param class-string<Type> $className * * @return void * * @throws Exception */ public static function overrideType($name, $className) { self::getTypeRegistry()->override($name, new $className()); } /** * Gets the (preferred) binding type for values of this type that * can be used when binding parameters to prepared statements. * * This method should return one of the {@link ParameterType} constants. * * @return int */ public function getBindingType() { return ParameterType::STRING; } /** * Gets the types array map which holds all registered types and the corresponding * type class * * @return array<string, string> */ public static function getTypesMap() { return array_map( static function (Type $type): string { return get_class($type); }, self::getTypeRegistry()->getMap() ); } /** * @deprecated Relying on string representation is discouraged and will be removed in DBAL 3.0. * * @return string */ public function __toString() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3258', 'Type::__toString() is deprecated, use Type::getName() or get_class($type) instead.' ); $type = static::class; $position = strrpos($type, '\\'); if ($position !== false) { $type = substr($type, $position); } return str_replace('Type', '', $type); } /** * Does working with this column require SQL conversion functions? * * This is a metadata function that is required for example in the ORM. * Usage of {@link convertToDatabaseValueSQL} and * {@link convertToPHPValueSQL} works for any type and mostly * does nothing. This method can additionally be used for optimization purposes. * * @return bool */ public function canRequireSQLConversion() { return false; } /** * Modifies the SQL expression (identifier, parameter) to convert to a database value. * * @param string $sqlExpr * * @return string */ public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) { return $sqlExpr; } /** * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. * * @param string $sqlExpr * @param AbstractPlatform $platform * * @return string */ public function convertToPHPValueSQL($sqlExpr, $platform) { return $sqlExpr; } /** * Gets an array of database types that map to this Doctrine type. * * @return string[] */ public function getMappedDatabaseTypes(AbstractPlatform $platform) { return []; } /** * If this Doctrine Type maps to an already mapped database type, * reverse schema engineering can't tell them apart. You need to mark * one of those types as commented, which will have Doctrine use an SQL * comment to typehint the actual Doctrine Type. * * @return bool */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return false; } } dbal/lib/Doctrine/DBAL/Types/TypeRegistry.php 0000644 00000005427 15120025742 0014762 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Exception; use function array_search; use function in_array; /** * The type registry is responsible for holding a map of all known DBAL types. * The types are stored using the flyweight pattern so that one type only exists as exactly one instance. */ final class TypeRegistry { /** @var array<string, Type> Map of type names and their corresponding flyweight objects. */ private $instances; /** * @param array<string, Type> $instances */ public function __construct(array $instances = []) { $this->instances = $instances; } /** * Finds a type by the given name. * * @throws Exception */ public function get(string $name): Type { if (! isset($this->instances[$name])) { throw Exception::unknownColumnType($name); } return $this->instances[$name]; } /** * Finds a name for the given type. * * @throws Exception */ public function lookupName(Type $type): string { $name = $this->findTypeName($type); if ($name === null) { throw Exception::typeNotRegistered($type); } return $name; } /** * Checks if there is a type of the given name. */ public function has(string $name): bool { return isset($this->instances[$name]); } /** * Registers a custom type to the type map. * * @throws Exception */ public function register(string $name, Type $type): void { if (isset($this->instances[$name])) { throw Exception::typeExists($name); } if ($this->findTypeName($type) !== null) { throw Exception::typeAlreadyRegistered($type); } $this->instances[$name] = $type; } /** * Overrides an already defined type to use a different implementation. * * @throws Exception */ public function override(string $name, Type $type): void { if (! isset($this->instances[$name])) { throw Exception::typeNotFound($name); } if (! in_array($this->findTypeName($type), [$name, null], true)) { throw Exception::typeAlreadyRegistered($type); } $this->instances[$name] = $type; } /** * Gets the map of all registered types and their corresponding type instances. * * @internal * * @return array<string, Type> */ public function getMap(): array { return $this->instances; } private function findTypeName(Type $type): ?string { $name = array_search($type, $this->instances, true); if ($name === false) { return null; } return $name; } } dbal/lib/Doctrine/DBAL/Types/Types.php 0000644 00000003214 15120025742 0013404 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL\Types; /** * Default built-in types provided by Doctrine DBAL. */ final class Types { public const ARRAY = 'array'; public const ASCII_STRING = 'ascii_string'; public const BIGINT = 'bigint'; public const BINARY = 'binary'; public const BLOB = 'blob'; public const BOOLEAN = 'boolean'; public const DATE_MUTABLE = 'date'; public const DATE_IMMUTABLE = 'date_immutable'; public const DATEINTERVAL = 'dateinterval'; public const DATETIME_MUTABLE = 'datetime'; public const DATETIME_IMMUTABLE = 'datetime_immutable'; public const DATETIMETZ_MUTABLE = 'datetimetz'; public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable'; public const DECIMAL = 'decimal'; public const FLOAT = 'float'; public const GUID = 'guid'; public const INTEGER = 'integer'; public const JSON = 'json'; public const OBJECT = 'object'; public const SIMPLE_ARRAY = 'simple_array'; public const SMALLINT = 'smallint'; public const STRING = 'string'; public const TEXT = 'text'; public const TIME_MUTABLE = 'time'; public const TIME_IMMUTABLE = 'time_immutable'; /** @deprecated json_array type is deprecated, use {@see self::JSON} instead. */ public const JSON_ARRAY = 'json_array'; /** * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Types/VarDateTimeImmutableType.php 0000644 00000002661 15120025742 0017154 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use function date_create_immutable; /** * Immutable type of {@see VarDateTimeType}. */ class VarDateTimeImmutableType extends VarDateTimeType { /** * {@inheritdoc} */ public function getName() { return Types::DATETIME_IMMUTABLE; } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { if ($value === null) { return $value; } if ($value instanceof DateTimeImmutable) { return $value->format($platform->getDateTimeFormatString()); } throw ConversionException::conversionFailedInvalidType( $value, $this->getName(), ['null', DateTimeImmutable::class] ); } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTimeImmutable) { return $value; } $dateTime = date_create_immutable($value); if (! $dateTime) { throw ConversionException::conversionFailed($value, $this->getName()); } return $dateTime; } /** * {@inheritdoc} */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; } } dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php 0000644 00000001575 15120025742 0015317 0 ustar 00 <?php namespace Doctrine\DBAL\Types; use DateTime; use Doctrine\DBAL\Platforms\AbstractPlatform; use function date_create; /** * Variable DateTime Type using date_create() instead of DateTime::createFromFormat(). * * This type has performance implications as it runs twice as long as the regular * {@see DateTimeType}, however in certain PostgreSQL configurations with * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. */ class VarDateTimeType extends DateTimeType { /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null || $value instanceof DateTime) { return $value; } $val = date_create($value); if (! $val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } } dbal/lib/Doctrine/DBAL/ColumnCase.php 0000644 00000001033 15120025742 0013222 0 ustar 00 <?php namespace Doctrine\DBAL; use PDO; /** * Contains portable column case conversions. */ final class ColumnCase { /** * Convert column names to upper case. * * @see \PDO::CASE_UPPER */ public const UPPER = PDO::CASE_UPPER; /** * Convert column names to lower case. * * @see \PDO::CASE_LOWER */ public const LOWER = PDO::CASE_LOWER; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Configuration.php 0000644 00000012337 15120025742 0014011 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\Cache\Cache; use Doctrine\DBAL\Logging\SQLLogger; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\Deprecations\Deprecation; use function preg_match; /** * Configuration container for the Doctrine DBAL. * * Internal note: When adding a new configuration option just write a getter/setter * pair and add the option to the _attributes array with a proper default value. */ class Configuration { /** * The attributes that are contained in the configuration. * Values are default values. * * @var mixed[] */ protected $_attributes = []; /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @return void */ public function setSQLLogger(?SQLLogger $logger = null) { $this->_attributes['sqlLogger'] = $logger; } /** * Gets the SQL logger that is used. * * @return SQLLogger|null */ public function getSQLLogger() { return $this->_attributes['sqlLogger'] ?? null; } /** * Gets the cache driver implementation that is used for query result caching. * * @return Cache|null */ public function getResultCacheImpl() { return $this->_attributes['resultCacheImpl'] ?? null; } /** * Sets the cache driver implementation that is used for query result caching. * * @return void */ public function setResultCacheImpl(Cache $cacheImpl) { $this->_attributes['resultCacheImpl'] = $cacheImpl; } /** * Sets the filter schema assets expression. * * Only include tables/sequences matching the filter expression regexp in * schema instances generated for the active connection when calling * {AbstractSchemaManager#createSchema()}. * * @deprecated Use Configuration::setSchemaAssetsFilter() instead * * @param string|null $filterExpression * * @return void */ public function setFilterSchemaAssetsExpression($filterExpression) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3316', 'Configuration::setFilterSchemaAssetsExpression() is deprecated, use setSchemaAssetsFilter() instead.' ); $this->_attributes['filterSchemaAssetsExpression'] = $filterExpression; if ($filterExpression) { $this->_attributes['filterSchemaAssetsExpressionCallable'] = $this->buildSchemaAssetsFilterFromExpression($filterExpression); } else { $this->_attributes['filterSchemaAssetsExpressionCallable'] = null; } } /** * Returns filter schema assets expression. * * @deprecated Use Configuration::getSchemaAssetsFilter() instead * * @return string|null */ public function getFilterSchemaAssetsExpression() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3316', 'Configuration::getFilterSchemaAssetsExpression() is deprecated, use getSchemaAssetsFilter() instead.' ); return $this->_attributes['filterSchemaAssetsExpression'] ?? null; } /** * @param string $filterExpression * * @return callable(string|AbstractAsset) */ private function buildSchemaAssetsFilterFromExpression($filterExpression): callable { return static function ($assetName) use ($filterExpression) { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return preg_match($filterExpression, $assetName); }; } /** * Sets the callable to use to filter schema assets. */ public function setSchemaAssetsFilter(?callable $callable = null): ?callable { $this->_attributes['filterSchemaAssetsExpression'] = null; return $this->_attributes['filterSchemaAssetsExpressionCallable'] = $callable; } /** * Returns the callable to use to filter schema assets. */ public function getSchemaAssetsFilter(): ?callable { return $this->_attributes['filterSchemaAssetsExpressionCallable'] ?? null; } /** * Sets the default auto-commit mode for connections. * * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either * the method commit or the method rollback. By default, new connections are in auto-commit mode. * * @see getAutoCommit * * @param bool $autoCommit True to enable auto-commit mode; false to disable it. * * @return void */ public function setAutoCommit($autoCommit) { $this->_attributes['autoCommit'] = (bool) $autoCommit; } /** * Returns the default auto-commit mode for connections. * * @see setAutoCommit * * @return bool True if auto-commit mode is enabled by default for connections, false otherwise. */ public function getAutoCommit() { return $this->_attributes['autoCommit'] ?? true; } } dbal/lib/Doctrine/DBAL/Connection.php 0000644 00000215237 15120025742 0013305 0 ustar 00 <?php namespace Doctrine\DBAL; use Closure; use Doctrine\Common\EventManager; use Doctrine\DBAL\Cache\ArrayStatement; use Doctrine\DBAL\Cache\CacheException; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Cache\ResultCacheStatement; use Doctrine\DBAL\Driver\Connection as DriverConnection; use Doctrine\DBAL\Driver\PDO\Statement as PDODriverStatement; use Doctrine\DBAL\Driver\PingableConnection; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Exception\ConnectionLost; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Exception\NoKeyValue; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use PDO; use Throwable; use Traversable; use function array_key_exists; use function array_shift; use function assert; use function func_get_args; use function implode; use function is_int; use function is_string; use function key; /** * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like * events, transaction isolation levels, configuration, emulated transaction nesting, * lazy connecting and more. * * @psalm-import-type Params from DriverManager * @psalm-consistent-constructor */ class Connection implements DriverConnection { /** * Constant for transaction isolation level READ UNCOMMITTED. * * @deprecated Use TransactionIsolationLevel::READ_UNCOMMITTED. */ public const TRANSACTION_READ_UNCOMMITTED = TransactionIsolationLevel::READ_UNCOMMITTED; /** * Constant for transaction isolation level READ COMMITTED. * * @deprecated Use TransactionIsolationLevel::READ_COMMITTED. */ public const TRANSACTION_READ_COMMITTED = TransactionIsolationLevel::READ_COMMITTED; /** * Constant for transaction isolation level REPEATABLE READ. * * @deprecated Use TransactionIsolationLevel::REPEATABLE_READ. */ public const TRANSACTION_REPEATABLE_READ = TransactionIsolationLevel::REPEATABLE_READ; /** * Constant for transaction isolation level SERIALIZABLE. * * @deprecated Use TransactionIsolationLevel::SERIALIZABLE. */ public const TRANSACTION_SERIALIZABLE = TransactionIsolationLevel::SERIALIZABLE; /** * Represents an array of ints to be expanded by Doctrine SQL parsing. */ public const PARAM_INT_ARRAY = ParameterType::INTEGER + self::ARRAY_PARAM_OFFSET; /** * Represents an array of strings to be expanded by Doctrine SQL parsing. */ public const PARAM_STR_ARRAY = ParameterType::STRING + self::ARRAY_PARAM_OFFSET; /** * Offset by which PARAM_* constants are detected as arrays of the param type. */ public const ARRAY_PARAM_OFFSET = 100; /** * The wrapped driver connection. * * @var \Doctrine\DBAL\Driver\Connection|null */ protected $_conn; /** @var Configuration */ protected $_config; /** @var EventManager */ protected $_eventManager; /** @var ExpressionBuilder */ protected $_expr; /** * The current auto-commit mode of this connection. * * @var bool */ private $autoCommit = true; /** * The transaction nesting level. * * @var int */ private $transactionNestingLevel = 0; /** * The currently active transaction isolation level or NULL before it has been determined. * * @var int|null */ private $transactionIsolationLevel; /** * If nested transactions should use savepoints. * * @var bool */ private $nestTransactionsWithSavepoints = false; /** * The parameters used during creation of the Connection instance. * * @var array<string,mixed> * @phpstan-var array<string,mixed> * @psalm-var Params */ private $params; /** * The database platform object used by the connection or NULL before it's initialized. * * @var AbstractPlatform|null */ private $platform; /** * The schema manager. * * @var AbstractSchemaManager|null */ protected $_schemaManager; /** * The used DBAL driver. * * @var Driver */ protected $_driver; /** * Flag that indicates whether the current transaction is marked for rollback only. * * @var bool */ private $isRollbackOnly = false; /** @var int */ protected $defaultFetchMode = FetchMode::ASSOCIATIVE; /** * Initializes a new instance of the Connection class. * * @internal The connection can be only instantiated by the driver manager. * * @param array<string,mixed> $params The connection parameters. * @param Driver $driver The driver to use. * @param Configuration|null $config The configuration, optional. * @param EventManager|null $eventManager The event manager, optional. * @psalm-param Params $params * @phpstan-param array<string,mixed> $params * * @throws Exception */ public function __construct( array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null ) { $this->_driver = $driver; $this->params = $params; if (isset($params['pdo'])) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3554', 'Passing a user provided PDO instance directly to Doctrine is deprecated.' ); if (! $params['pdo'] instanceof PDO) { throw Exception::invalidPdoInstance(); } $this->_conn = $params['pdo']; $this->_conn->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDODriverStatement::class, []]); unset($this->params['pdo']); } if (isset($params['platform'])) { if (! $params['platform'] instanceof Platforms\AbstractPlatform) { throw Exception::invalidPlatformType($params['platform']); } $this->platform = $params['platform']; } // Create default config and event manager if none given if (! $config) { $config = new Configuration(); } if (! $eventManager) { $eventManager = new EventManager(); } $this->_config = $config; $this->_eventManager = $eventManager; $this->_expr = new Query\Expression\ExpressionBuilder($this); $this->autoCommit = $config->getAutoCommit(); } /** * Gets the parameters used during instantiation. * * @internal * * @return array<string,mixed> * @psalm-return Params * @phpstan-return array<string,mixed> */ public function getParams() { return $this->params; } /** * Gets the name of the database this Connection is connected to. * * @return string */ public function getDatabase() { return $this->_driver->getDatabase($this); } /** * Gets the hostname of the currently connected database. * * @deprecated * * @return string|null */ public function getHost() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Connection::getHost() is deprecated, get the database server host from application config ' . 'or as a last resort from internal Connection::getParams() API.' ); return $this->params['host'] ?? null; } /** * Gets the port of the currently connected database. * * @deprecated * * @return mixed */ public function getPort() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Connection::getPort() is deprecated, get the database server port from application config ' . 'or as a last resort from internal Connection::getParams() API.' ); return $this->params['port'] ?? null; } /** * Gets the username used by this connection. * * @deprecated * * @return string|null */ public function getUsername() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Connection::getUsername() is deprecated, get the username from application config ' . 'or as a last resort from internal Connection::getParams() API.' ); return $this->params['user'] ?? null; } /** * Gets the password used by this connection. * * @deprecated * * @return string|null */ public function getPassword() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/3580', 'Connection::getPassword() is deprecated, get the password from application config ' . 'or as a last resort from internal Connection::getParams() API.' ); return $this->params['password'] ?? null; } /** * Gets the DBAL driver instance. * * @return Driver */ public function getDriver() { return $this->_driver; } /** * Gets the Configuration used by the Connection. * * @return Configuration */ public function getConfiguration() { return $this->_config; } /** * Gets the EventManager used by the Connection. * * @return EventManager */ public function getEventManager() { return $this->_eventManager; } /** * Gets the DatabasePlatform for the connection. * * @return AbstractPlatform * * @throws Exception */ public function getDatabasePlatform() { if ($this->platform === null) { $this->platform = $this->detectDatabasePlatform(); $this->platform->setEventManager($this->_eventManager); } return $this->platform; } /** * Gets the ExpressionBuilder for the connection. * * @return ExpressionBuilder */ public function getExpressionBuilder() { return $this->_expr; } /** * Establishes the connection with the database. * * @return bool TRUE if the connection was successfully established, FALSE if * the connection is already open. */ public function connect() { if ($this->_conn !== null) { return false; } $driverOptions = $this->params['driverOptions'] ?? []; $user = $this->params['user'] ?? null; $password = $this->params['password'] ?? null; $this->_conn = $this->_driver->connect($this->params, $user, $password, $driverOptions); $this->transactionNestingLevel = 0; if ($this->autoCommit === false) { $this->beginTransaction(); } if ($this->_eventManager->hasListeners(Events::postConnect)) { $eventArgs = new Event\ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Detects and sets the database platform. * * Evaluates custom platform class and version in order to set the correct platform. * * @throws Exception If an invalid platform was specified for this connection. */ private function detectDatabasePlatform(): AbstractPlatform { $version = $this->getDatabasePlatformVersion(); if ($version !== null) { assert($this->_driver instanceof VersionAwarePlatformDriver); return $this->_driver->createDatabasePlatformForVersion($version); } return $this->_driver->getDatabasePlatform(); } /** * Returns the version of the related platform if applicable. * * Returns null if either the driver is not capable to create version * specific platform instances, no explicit server version was specified * or the underlying driver connection cannot determine the platform * version without having to query it (performance reasons). * * @return string|null * * @throws Throwable */ private function getDatabasePlatformVersion() { // Driver does not support version specific platforms. if (! $this->_driver instanceof VersionAwarePlatformDriver) { return null; } // Explicit platform version requested (supersedes auto-detection). if (isset($this->params['serverVersion'])) { return $this->params['serverVersion']; } // If not connected, we need to connect now to determine the platform version. if ($this->_conn === null) { try { $this->connect(); } catch (Throwable $originalException) { if (empty($this->params['dbname'])) { throw $originalException; } // The database to connect to might not yet exist. // Retry detection without database name connection parameter. $params = $this->params; unset($this->params['dbname']); try { $this->connect(); } catch (Throwable $fallbackException) { // Either the platform does not support database-less connections // or something else went wrong. throw $originalException; } finally { $this->params = $params; } $serverVersion = $this->getServerVersion(); // Close "temporary" connection to allow connecting to the real database again. $this->close(); return $serverVersion; } } return $this->getServerVersion(); } /** * Returns the database server version if the underlying driver supports it. * * @return string|null */ private function getServerVersion() { $connection = $this->getWrappedConnection(); // Automatic platform version detection. if ($connection instanceof ServerInfoAwareConnection && ! $connection->requiresQueryForServerVersion()) { return $connection->getServerVersion(); } // Unable to detect platform version. return null; } /** * Returns the current auto-commit mode for this connection. * * @see setAutoCommit * * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise. */ public function isAutoCommit() { return $this->autoCommit === true; } /** * Sets auto-commit mode for this connection. * * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either * the method commit or the method rollback. By default, new connections are in auto-commit mode. * * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op. * * @see isAutoCommit * * @param bool $autoCommit True to enable auto-commit mode; false to disable it. * * @return void */ public function setAutoCommit($autoCommit) { $autoCommit = (bool) $autoCommit; // Mode not changed, no-op. if ($autoCommit === $this->autoCommit) { return; } $this->autoCommit = $autoCommit; // Commit all currently active transactions if any when switching auto-commit mode. if ($this->_conn === null || $this->transactionNestingLevel === 0) { return; } $this->commitAll(); } /** * Sets the fetch mode. * * @deprecated Use one of the fetch- or iterate-related methods. * * @param int $fetchMode * * @return void */ public function setFetchMode($fetchMode) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Default Fetch Mode configuration is deprecated, use explicit Connection::fetch*() APIs instead.' ); $this->defaultFetchMode = $fetchMode; } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @deprecated Use fetchAssociative() * * @param string $sql SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<string, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchAssoc($sql, array $params = [], array $types = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Connection::fetchAssoc() is deprecated, use Connection::fetchAssociative() API instead.' ); return $this->executeQuery($sql, $params, $types)->fetch(FetchMode::ASSOCIATIVE); } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @deprecated Use fetchNumeric() * * @param string $sql SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int, mixed>|false False is returned if no rows are found. */ public function fetchArray($sql, array $params = [], array $types = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Connection::fetchArray() is deprecated, use Connection::fetchNumeric() API instead.' ); return $this->executeQuery($sql, $params, $types)->fetch(FetchMode::NUMERIC); } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @deprecated Use fetchOne() instead. * * @param string $sql SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param int $column 0-indexed column number * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return mixed|false False is returned if no rows are found. * * @throws Exception */ public function fetchColumn($sql, array $params = [], $column = 0, array $types = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Connection::fetchColumn() is deprecated, use Connection::fetchOne() API instead.' ); return $this->executeQuery($sql, $params, $types)->fetchColumn($column); } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<string, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchAssociative(string $query, array $params = [], array $types = []) { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchAssociative(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int, mixed>|false False is returned if no rows are found. * * @throws Exception */ public function fetchNumeric(string $query, array $params = [], array $types = []) { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchNumeric(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return mixed|false False is returned if no rows are found. * * @throws Exception */ public function fetchOne(string $query, array $params = [], array $types = []) { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchOne(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Whether an actual connection to the database is established. * * @return bool */ public function isConnected() { return $this->_conn !== null; } /** * Checks whether a transaction is currently active. * * @return bool TRUE if a transaction is currently active, FALSE otherwise. */ public function isTransactionActive() { return $this->transactionNestingLevel > 0; } /** * Adds condition based on the criteria to the query components * * @param array<string,mixed> $criteria Map of key columns to their values * @param string[] $columns Column names * @param mixed[] $values Column values * @param string[] $conditions Key conditions * * @throws Exception */ private function addCriteriaCondition( array $criteria, array &$columns, array &$values, array &$conditions ): void { $platform = $this->getDatabasePlatform(); foreach ($criteria as $columnName => $value) { if ($value === null) { $conditions[] = $platform->getIsNullExpression($columnName); continue; } $columns[] = $columnName; $values[] = $value; $conditions[] = $columnName . ' = ?'; } } /** * Executes an SQL DELETE statement on a table. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $criteria Deletion criteria * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function delete($table, array $criteria, array $types = []) { if (empty($criteria)) { throw InvalidArgumentException::fromEmptyCriteria(); } $columns = $values = $conditions = []; $this->addCriteriaCondition($criteria, $columns, $values, $conditions); return $this->executeStatement( 'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions), $values, is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types ); } /** * Closes the connection. * * @return void */ public function close() { $this->_conn = null; } /** * Sets the transaction isolation level. * * @param int $level The level to set. * * @return int|string */ public function setTransactionIsolation($level) { $this->transactionIsolationLevel = $level; return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level)); } /** * Gets the currently active transaction isolation level. * * @return int The current transaction isolation level. */ public function getTransactionIsolation() { if ($this->transactionIsolationLevel === null) { $this->transactionIsolationLevel = $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel(); } return $this->transactionIsolationLevel; } /** * Executes an SQL UPDATE statement on a table. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $data Column-value pairs * @param array<string, mixed> $criteria Update criteria * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function update($table, array $data, array $criteria, array $types = []) { $columns = $values = $conditions = $set = []; foreach ($data as $columnName => $value) { $columns[] = $columnName; $values[] = $value; $set[] = $columnName . ' = ?'; } $this->addCriteriaCondition($criteria, $columns, $values, $conditions); if (is_string(key($types))) { $types = $this->extractTypeValues($columns, $types); } $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' AND ', $conditions); return $this->executeStatement($sql, $values, $types); } /** * Inserts a table row with specified data. * * Table expression and columns are not escaped and are not safe for user-input. * * @param string $table Table name * @param array<string, mixed> $data Column-value pairs * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function insert($table, array $data, array $types = []) { if (empty($data)) { return $this->executeStatement('INSERT INTO ' . $table . ' () VALUES ()'); } $columns = []; $values = []; $set = []; foreach ($data as $columnName => $value) { $columns[] = $columnName; $values[] = $value; $set[] = '?'; } return $this->executeStatement( 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' . ' VALUES (' . implode(', ', $set) . ')', $values, is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types ); } /** * Extract ordered type list from an ordered column list and type map. * * @param array<int, string> $columnList * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> */ private function extractTypeValues(array $columnList, array $types) { $typeValues = []; foreach ($columnList as $columnIndex => $columnName) { $typeValues[] = $types[$columnName] ?? ParameterType::STRING; } return $typeValues; } /** * Quotes a string so it can be safely used as a table or column name, even if * it is a reserved name. * * Delimiting style depends on the underlying database platform that is being used. * * NOTE: Just because you CAN use quoted identifiers does not mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The name to be quoted. * * @return string The quoted name. */ public function quoteIdentifier($str) { return $this->getDatabasePlatform()->quoteIdentifier($str); } /** * {@inheritDoc} * * @param mixed $value * @param int|string|Type|null $type */ public function quote($value, $type = ParameterType::STRING) { $connection = $this->getWrappedConnection(); [$value, $bindingType] = $this->getBindingInfo($value, $type); return $connection->quote($value, $bindingType); } /** * Prepares and executes an SQL query and returns the result as an associative array. * * @deprecated Use fetchAllAssociative() * * @param string $sql The SQL query. * @param mixed[] $params The query parameters. * @param int[]|string[] $types The query parameter types. * * @return mixed[] */ public function fetchAll($sql, array $params = [], $types = []) { return $this->executeQuery($sql, $params, $types)->fetchAll(); } /** * Prepares and executes an SQL query and returns the result as an array of numeric arrays. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int,array<int,mixed>> * * @throws Exception */ public function fetchAllNumeric(string $query, array $params = [], array $types = []): array { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchAllNumeric(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the result as an array of associative arrays. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociative(string $query, array $params = [], array $types = []): array { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchAllAssociative(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys * mapped to the first column and the values mapped to the second column. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string>|array<string, int|string> $types Parameter types * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array { $stmt = $this->executeQuery($query, $params, $types); $this->ensureHasKeyValue($stmt); $data = []; foreach ($stmt->fetchAll(FetchMode::NUMERIC) as [$key, $value]) { $data[$key] = $value; } return $data; } /** * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped * to the first column and the values being an associative array representing the rest of the columns * and their values. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string>|array<string, int|string> $types Parameter types * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array { $stmt = $this->executeQuery($query, $params, $types); $data = []; foreach ($stmt->fetchAll(FetchMode::ASSOCIATIVE) as $row) { $data[array_shift($row)] = $row; } return $data; } /** * Prepares and executes an SQL query and returns the result as an array of the first column values. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int,mixed> * * @throws Exception */ public function fetchFirstColumn(string $query, array $params = [], array $types = []): array { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); return $stmt->fetchFirstColumn(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,array<int,mixed>> * * @throws Exception */ public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); yield from $stmt->iterateNumeric(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the result as an iterator over rows represented * as associative arrays. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,array<string,mixed>> * * @throws Exception */ public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); yield from $stmt->iterateAssociative(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares and executes an SQL query and returns the result as an iterator with the keys * mapped to the first column and the values mapped to the second column. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string>|array<string, int|string> $types Parameter types * * @return Traversable<mixed,mixed> * * @throws Exception */ public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable { $stmt = $this->executeQuery($query, $params, $types); $this->ensureHasKeyValue($stmt); while (($row = $stmt->fetch(FetchMode::NUMERIC)) !== false) { yield $row[0] => $row[1]; } } /** * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped * to the first column and the values being an associative array representing the rest of the columns * and their values. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string>|array<string, int|string> $types Parameter types * * @return Traversable<mixed,array<string,mixed>> * * @throws Exception */ public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable { $stmt = $this->executeQuery($query, $params, $types); while (($row = $stmt->fetch(FetchMode::ASSOCIATIVE)) !== false) { yield array_shift($row) => $row; } } /** * Prepares and executes an SQL query and returns the result as an iterator over the first column values. * * @param string $query SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return Traversable<int,mixed> * * @throws Exception */ public function iterateColumn(string $query, array $params = [], array $types = []): Traversable { try { $stmt = $this->ensureForwardCompatibilityStatement( $this->executeQuery($query, $params, $types) ); yield from $stmt->iterateColumn(); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $query, $params, $types); } } /** * Prepares an SQL statement. * * @param string $sql The SQL statement to prepare. * * @return Statement The prepared statement. * * @throws Exception */ public function prepare($sql) { try { $stmt = new Statement($sql, $this); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $sql); } $stmt->setFetchMode($this->defaultFetchMode); return $stmt; } /** * Executes an, optionally parametrized, SQL query. * * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * * @param string $sql SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return ForwardCompatibility\DriverStatement|ForwardCompatibility\DriverResultStatement * * The executed statement or the cached result statement if a query cache profile is used * * @throws Exception */ public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null) { if ($qcp !== null) { return $this->executeCacheQuery($sql, $params, $types, $qcp); } $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($logger) { $logger->startQuery($sql, $params, $types); } try { if ($params) { [$sql, $params, $types] = SQLParserUtils::expandListParameters($sql, $params, $types); $stmt = $connection->prepare($sql); if ($types) { $this->_bindTypedValues($stmt, $params, $types); $stmt->execute(); } else { $stmt->execute($params); } } else { $stmt = $connection->query($sql); } } catch (Throwable $e) { $this->handleExceptionDuringQuery( $e, $sql, $params, $types ); } $stmt->setFetchMode($this->defaultFetchMode); if ($logger) { $logger->stopQuery(); } return $this->ensureForwardCompatibilityStatement($stmt); } /** * Executes a caching query. * * @param string $sql SQL query * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return ForwardCompatibility\DriverResultStatement * * @throws CacheException */ public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp) { $resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl(); if ($resultCache === null) { throw CacheException::noResultDriverConfigured(); } $connectionParams = $this->params; unset($connectionParams['platform']); [$cacheKey, $realKey] = $qcp->generateCacheKeys($sql, $params, $types, $connectionParams); // fetch the row pointers entry $data = $resultCache->fetch($cacheKey); if ($data !== false) { // is the real key part of this row pointers map or is the cache only pointing to other cache keys? if (isset($data[$realKey])) { $stmt = new ArrayStatement($data[$realKey]); } elseif (array_key_exists($realKey, $data)) { $stmt = new ArrayStatement([]); } } if (! isset($stmt)) { $stmt = new ResultCacheStatement( $this->executeQuery($sql, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime() ); } $stmt->setFetchMode($this->defaultFetchMode); return $this->ensureForwardCompatibilityStatement($stmt); } /** * @return ForwardCompatibility\Result */ private function ensureForwardCompatibilityStatement(ResultStatement $stmt) { return ForwardCompatibility\Result::ensure($stmt); } /** * Executes an, optionally parametrized, SQL query and returns the result, * applying a given projection/transformation function on each row of the result. * * @deprecated * * @param string $sql The SQL query to execute. * @param mixed[] $params The parameters, if any. * @param Closure $function The transformation function that is applied on each row. * The function receives a single parameter, an array, that * represents a row of the result set. * * @return mixed[] The projected result of the query. */ public function project($sql, array $params, Closure $function) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3823', 'Connection::project() is deprecated without replacement, implement data projections in your own code.' ); $result = []; $stmt = $this->executeQuery($sql, $params); while ($row = $stmt->fetch()) { $result[] = $function($row); } $stmt->closeCursor(); return $result; } /** * Executes an SQL statement, returning a result set as a Statement object. * * @deprecated Use {@link executeQuery()} instead. * * @return \Doctrine\DBAL\Driver\Statement * * @throws Exception */ public function query() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', 'Connection::query() is deprecated, use Connection::executeQuery() instead.' ); $connection = $this->getWrappedConnection(); $args = func_get_args(); $logger = $this->_config->getSQLLogger(); if ($logger) { $logger->startQuery($args[0]); } try { $statement = $connection->query(...$args); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $args[0]); } $statement->setFetchMode($this->defaultFetchMode); if ($logger) { $logger->stopQuery(); } return $statement; } /** * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters * and returns the number of affected rows. * * This method supports PDO binding types as well as DBAL mapping types. * * @deprecated Use {@link executeStatement()} instead. * * @param string $sql SQL statement * @param array<int, mixed>|array<string, mixed> $params Statement parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function executeUpdate($sql, array $params = [], array $types = []) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', 'Connection::executeUpdate() is deprecated, use Connection::executeStatement() instead.' ); return $this->executeStatement($sql, $params, $types); } /** * Executes an SQL statement with the given parameters and returns the number of affected rows. * * Could be used for: * - DML statements: INSERT, UPDATE, DELETE, etc. * - DDL statements: CREATE, DROP, ALTER, etc. * - DCL statements: GRANT, REVOKE, etc. * - Session control statements: ALTER SESSION, SET, DECLARE, etc. * - Other statements that don't yield a row set. * * This method supports PDO binding types as well as DBAL mapping types. * * @param string $sql SQL statement * @param array<int, mixed>|array<string, mixed> $params Statement parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return int|string The number of affected rows. * * @throws Exception */ public function executeStatement($sql, array $params = [], array $types = []) { $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($logger) { $logger->startQuery($sql, $params, $types); } try { if ($params) { [$sql, $params, $types] = SQLParserUtils::expandListParameters($sql, $params, $types); $stmt = $connection->prepare($sql); if ($types) { $this->_bindTypedValues($stmt, $params, $types); $stmt->execute(); } else { $stmt->execute($params); } $result = $stmt->rowCount(); } else { $result = $connection->exec($sql); } } catch (Throwable $e) { $this->handleExceptionDuringQuery( $e, $sql, $params, $types ); } if ($logger) { $logger->stopQuery(); } return $result; } /** * Executes an SQL statement and return the number of affected rows. * * @deprecated Use {@link executeStatement()} instead. * * @param string $sql * * @return int|string The number of affected rows. * * @throws Exception */ public function exec($sql) { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4163', 'Connection::exec() is deprecated, use Connection::executeStatement() instead.' ); $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($logger) { $logger->startQuery($sql); } try { $result = $connection->exec($sql); } catch (Throwable $e) { $this->handleExceptionDuringQuery($e, $sql); } if ($logger) { $logger->stopQuery(); } return $result; } /** * Returns the current transaction nesting level. * * @return int The nesting level. A value of 0 means there's no active transaction. */ public function getTransactionNestingLevel() { return $this->transactionNestingLevel; } /** * Fetches the SQLSTATE associated with the last database operation. * * @deprecated The error information is available via exceptions. * * @return string|null The last error code. */ public function errorCode() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3507', 'Connection::errorCode() is deprecated, use getCode() or getSQLState() on Exception instead.' ); return $this->getWrappedConnection()->errorCode(); } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3507', 'Connection::errorInfo() is deprecated, use getCode() or getSQLState() on Exception instead.' ); return $this->getWrappedConnection()->errorInfo(); } /** * Returns the ID of the last inserted row, or the last value from a sequence object, * depending on the underlying driver. * * Note: This method may not return a meaningful or consistent result across different drivers, * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY * columns or sequences. * * @param string|null $name Name of the sequence object from which the ID should be returned. * * @return string|int|false A string representation of the last inserted ID. */ public function lastInsertId($name = null) { return $this->getWrappedConnection()->lastInsertId($name); } /** * Executes a function in a transaction. * * The function gets passed this Connection instance as an (optional) parameter. * * If an exception occurs during execution of the function or transaction commit, * the transaction is rolled back and the exception re-thrown. * * @param Closure $func The function to execute transactionally. * * @return mixed The value returned by $func * * @throws Throwable */ public function transactional(Closure $func) { $this->beginTransaction(); try { $res = $func($this); $this->commit(); return $res; } catch (Throwable $e) { $this->rollBack(); throw $e; } } /** * Sets if nested transactions should use savepoints. * * @param bool $nestTransactionsWithSavepoints * * @return void * * @throws ConnectionException */ public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) { if ($this->transactionNestingLevel > 0) { throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); } if (! $this->getDatabasePlatform()->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints; } /** * Gets if nested transactions should use savepoints. * * @return bool */ public function getNestTransactionsWithSavepoints() { return $this->nestTransactionsWithSavepoints; } /** * Returns the savepoint name to use for nested transactions are false if they are not supported * "savepointFormat" parameter is not set * * @return mixed A string with the savepoint name or false. */ protected function _getNestedTransactionSavePointName() { return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel; } /** * {@inheritDoc} */ public function beginTransaction() { $connection = $this->getWrappedConnection(); ++$this->transactionNestingLevel; $logger = $this->_config->getSQLLogger(); if ($this->transactionNestingLevel === 1) { if ($logger) { $logger->startQuery('"START TRANSACTION"'); } $connection->beginTransaction(); if ($logger) { $logger->stopQuery(); } } elseif ($this->nestTransactionsWithSavepoints) { if ($logger) { $logger->startQuery('"SAVEPOINT"'); } $this->createSavepoint($this->_getNestedTransactionSavePointName()); if ($logger) { $logger->stopQuery(); } } return true; } /** * {@inheritDoc} * * @throws ConnectionException If the commit failed due to no active transaction or * because the transaction was marked for rollback only. */ public function commit() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } if ($this->isRollbackOnly) { throw ConnectionException::commitFailedRollbackOnly(); } $result = true; $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($this->transactionNestingLevel === 1) { if ($logger) { $logger->startQuery('"COMMIT"'); } $result = $connection->commit(); if ($logger) { $logger->stopQuery(); } } elseif ($this->nestTransactionsWithSavepoints) { if ($logger) { $logger->startQuery('"RELEASE SAVEPOINT"'); } $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); if ($logger) { $logger->stopQuery(); } } --$this->transactionNestingLevel; if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) { return $result; } $this->beginTransaction(); return $result; } /** * Commits all current nesting transactions. */ private function commitAll(): void { while ($this->transactionNestingLevel !== 0) { if ($this->autoCommit === false && $this->transactionNestingLevel === 1) { // When in no auto-commit mode, the last nesting commit immediately starts a new transaction. // Therefore we need to do the final commit here and then leave to avoid an infinite loop. $this->commit(); return; } $this->commit(); } } /** * Cancels any database changes done during the current transaction. * * @return bool * * @throws ConnectionException If the rollback operation failed. */ public function rollBack() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } $connection = $this->getWrappedConnection(); $logger = $this->_config->getSQLLogger(); if ($this->transactionNestingLevel === 1) { if ($logger) { $logger->startQuery('"ROLLBACK"'); } $this->transactionNestingLevel = 0; $connection->rollBack(); $this->isRollbackOnly = false; if ($logger) { $logger->stopQuery(); } if ($this->autoCommit === false) { $this->beginTransaction(); } } elseif ($this->nestTransactionsWithSavepoints) { if ($logger) { $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); } $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); --$this->transactionNestingLevel; if ($logger) { $logger->stopQuery(); } } else { $this->isRollbackOnly = true; --$this->transactionNestingLevel; } return true; } /** * Creates a new savepoint. * * @param string $savepoint The name of the savepoint to create. * * @return void * * @throws ConnectionException */ public function createSavepoint($savepoint) { $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->getWrappedConnection()->exec($platform->createSavePoint($savepoint)); } /** * Releases the given savepoint. * * @param string $savepoint The name of the savepoint to release. * * @return void * * @throws ConnectionException */ public function releaseSavepoint($savepoint) { $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } if (! $platform->supportsReleaseSavepoints()) { return; } $this->getWrappedConnection()->exec($platform->releaseSavePoint($savepoint)); } /** * Rolls back to the given savepoint. * * @param string $savepoint The name of the savepoint to rollback to. * * @return void * * @throws ConnectionException */ public function rollbackSavepoint($savepoint) { $platform = $this->getDatabasePlatform(); if (! $platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->getWrappedConnection()->exec($platform->rollbackSavePoint($savepoint)); } /** * Gets the wrapped driver connection. * * @return DriverConnection */ public function getWrappedConnection() { $this->connect(); assert($this->_conn !== null); return $this->_conn; } /** * Gets the SchemaManager that can be used to inspect or change the * database schema through the connection. * * @return AbstractSchemaManager */ public function getSchemaManager() { if ($this->_schemaManager === null) { $this->_schemaManager = $this->_driver->getSchemaManager($this); } return $this->_schemaManager; } /** * Marks the current transaction so that the only possible * outcome for the transaction to be rolled back. * * @return void * * @throws ConnectionException If no transaction is active. */ public function setRollbackOnly() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } $this->isRollbackOnly = true; } /** * Checks whether the current transaction is marked for rollback only. * * @return bool * * @throws ConnectionException If no transaction is active. */ public function isRollbackOnly() { if ($this->transactionNestingLevel === 0) { throw ConnectionException::noActiveTransaction(); } return $this->isRollbackOnly; } /** * Converts a given value to its database representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * * @return mixed The converted value. */ public function convertToDatabaseValue($value, $type) { return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform()); } /** * Converts a given value to its PHP representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * * @return mixed The converted type. */ public function convertToPHPValue($value, $type) { return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform()); } /** * Binds a set of parameters, some or all of which are typed with a PDO binding type * or DBAL mapping type, to a given statement. * * @internal Duck-typing used on the $stmt parameter to support driver statements as well as * raw PDOStatement instances. * * @param \Doctrine\DBAL\Driver\Statement $stmt Prepared statement * @param array<int, mixed>|array<string, mixed> $params Statement parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return void */ private function _bindTypedValues($stmt, array $params, array $types) { // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. if (is_int(key($params))) { // Positional parameters $typeOffset = array_key_exists(0, $types) ? -1 : 0; $bindIndex = 1; foreach ($params as $value) { $typeIndex = $bindIndex + $typeOffset; if (isset($types[$typeIndex])) { $type = $types[$typeIndex]; [$value, $bindingType] = $this->getBindingInfo($value, $type); $stmt->bindValue($bindIndex, $value, $bindingType); } else { $stmt->bindValue($bindIndex, $value); } ++$bindIndex; } } else { // Named parameters foreach ($params as $name => $value) { if (isset($types[$name])) { $type = $types[$name]; [$value, $bindingType] = $this->getBindingInfo($value, $type); $stmt->bindValue($name, $value, $bindingType); } else { $stmt->bindValue($name, $value); } } } } /** * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type. * * @param mixed $value The value to bind. * @param int|string|Type|null $type The type to bind (PDO or DBAL). * * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type. */ private function getBindingInfo($value, $type): array { if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->getDatabasePlatform()); $bindingType = $type->getBindingType(); } else { $bindingType = $type ?? ParameterType::STRING; } return [$value, $bindingType]; } /** * Resolves the parameters to a format which can be displayed. * * @internal This is a purely internal method. If you rely on this method, you are advised to * copy/paste the code as this method may change, or be removed without prior notice. * * @param array<int, mixed>|array<string, mixed> $params Query parameters * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types * * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> */ public function resolveParams(array $params, array $types) { $resolvedParams = []; // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. if (is_int(key($params))) { // Positional parameters $typeOffset = array_key_exists(0, $types) ? -1 : 0; $bindIndex = 1; foreach ($params as $value) { $typeIndex = $bindIndex + $typeOffset; if (isset($types[$typeIndex])) { $type = $types[$typeIndex]; [$value] = $this->getBindingInfo($value, $type); $resolvedParams[$bindIndex] = $value; } else { $resolvedParams[$bindIndex] = $value; } ++$bindIndex; } } else { // Named parameters foreach ($params as $name => $value) { if (isset($types[$name])) { $type = $types[$name]; [$value] = $this->getBindingInfo($value, $type); $resolvedParams[$name] = $value; } else { $resolvedParams[$name] = $value; } } } return $resolvedParams; } /** * Creates a new instance of a SQL query builder. * * @return QueryBuilder */ public function createQueryBuilder() { return new Query\QueryBuilder($this); } /** * Ping the server * * When the server is not available the method returns FALSE. * It is responsibility of the developer to handle this case * and abort the request or reconnect manually: * * @deprecated * * @return bool * * @example * * if ($conn->ping() === false) { * $conn->close(); * $conn->connect(); * } * * It is undefined if the underlying driver attempts to reconnect * or disconnect when the connection is not available anymore * as long it returns TRUE when a reconnect succeeded and * FALSE when the connection was dropped. */ public function ping() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4119', 'Retry and reconnecting lost connections now happens automatically, ping() will be removed in DBAL 3.' ); $connection = $this->getWrappedConnection(); if ($connection instanceof PingableConnection) { return $connection->ping(); } try { $this->query($this->getDatabasePlatform()->getDummySelectSQL()); return true; } catch (DBALException $e) { return false; } } /** * @internal * * @param array<int, mixed>|array<string, mixed> $params * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types * * @psalm-return never-return * * @throws Exception */ public function handleExceptionDuringQuery(Throwable $e, string $sql, array $params = [], array $types = []): void { $this->throw( Exception::driverExceptionDuringQuery( $this->_driver, $e, $sql, $this->resolveParams($params, $types) ) ); } /** * @internal * * @psalm-return never-return * * @throws Exception */ public function handleDriverException(Throwable $e): void { $this->throw( Exception::driverException( $this->_driver, $e ) ); } /** * @internal * * @psalm-return never-return * * @throws Exception */ private function throw(Exception $e): void { if ($e instanceof ConnectionLost) { $this->close(); } throw $e; } private function ensureHasKeyValue(ResultStatement $stmt): void { $columnCount = $stmt->columnCount(); if ($columnCount < 2) { throw NoKeyValue::fromColumnCount($columnCount); } } } dbal/lib/Doctrine/DBAL/ConnectionException.php 0000644 00000001711 15120025742 0015152 0 ustar 00 <?php namespace Doctrine\DBAL; /** * @psalm-immutable */ class ConnectionException extends Exception { /** * @return ConnectionException */ public static function commitFailedRollbackOnly() { return new self('Transaction commit failed because the transaction has been marked for rollback only.'); } /** * @return ConnectionException */ public static function noActiveTransaction() { return new self('There is no active transaction.'); } /** * @return ConnectionException */ public static function savepointsNotSupported() { return new self('Savepoints are not supported by this driver.'); } /** * @return ConnectionException */ public static function mayNotAlterNestedTransactionWithSavepointsInTransaction() { return new self('May not alter the nested transaction with savepoints behavior while a transaction is open.'); } } dbal/lib/Doctrine/DBAL/DBALException.php 0000644 00000022034 15120025742 0013556 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Driver\DriverException as DeprecatedDriverException; use Doctrine\DBAL\Driver\ExceptionConverterDriver; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Throwable; use function array_map; use function bin2hex; use function get_class; use function gettype; use function implode; use function is_object; use function is_resource; use function is_string; use function json_encode; use function preg_replace; use function spl_object_hash; use function sprintf; /** * @deprecated Use {@link Exception} instead * * @psalm-immutable */ class DBALException extends \Exception { /** * @param string $method * * @return Exception */ public static function notSupported($method) { return new Exception(sprintf("Operation '%s' is not supported by platform.", $method)); } /** * @deprecated Use {@link invalidPlatformType()} instead. */ public static function invalidPlatformSpecified(): self { return new Exception( "Invalid 'platform' option specified, need to give an instance of " . AbstractPlatform::class . '.' ); } /** * @param mixed $invalidPlatform */ public static function invalidPlatformType($invalidPlatform): self { if (is_object($invalidPlatform)) { return new Exception( sprintf( "Option 'platform' must be a subtype of '%s', instance of '%s' given", AbstractPlatform::class, get_class($invalidPlatform) ) ); } return new Exception( sprintf( "Option 'platform' must be an object and subtype of '%s'. Got '%s'", AbstractPlatform::class, gettype($invalidPlatform) ) ); } /** * Returns a new instance for an invalid specified platform version. * * @param string $version The invalid platform version given. * @param string $expectedFormat The expected platform version format. * * @return Exception */ public static function invalidPlatformVersionSpecified($version, $expectedFormat) { return new Exception( sprintf( 'Invalid platform version "%s" specified. ' . 'The platform version has to be specified in the format: "%s".', $version, $expectedFormat ) ); } /** * @deprecated Passing a PDO instance in connection parameters is deprecated. * * @return Exception */ public static function invalidPdoInstance() { return new Exception( "The 'pdo' option was used in DriverManager::getConnection() but no " . 'instance of PDO was given.' ); } /** * @param string|null $url The URL that was provided in the connection parameters (if any). * * @return Exception */ public static function driverRequired($url = null) { if ($url) { return new Exception( sprintf( "The options 'driver' or 'driverClass' are mandatory if a connection URL without scheme " . 'is given to DriverManager::getConnection(). Given URL: %s', $url ) ); } return new Exception("The options 'driver' or 'driverClass' are mandatory if no PDO " . 'instance is given to DriverManager::getConnection().'); } /** * @param string $unknownDriverName * @param string[] $knownDrivers * * @return Exception */ public static function unknownDriver($unknownDriverName, array $knownDrivers) { return new Exception("The given 'driver' " . $unknownDriverName . ' is unknown, ' . 'Doctrine currently supports only the following drivers: ' . implode(', ', $knownDrivers)); } /** * @deprecated * * @param string $sql * @param mixed[] $params * * @return Exception */ public static function driverExceptionDuringQuery(Driver $driver, Throwable $driverEx, $sql, array $params = []) { $msg = "An exception occurred while executing '" . $sql . "'"; if ($params) { $msg .= ' with params ' . self::formatParameters($params); } $msg .= ":\n\n" . $driverEx->getMessage(); return self::wrapException($driver, $driverEx, $msg); } /** * @deprecated * * @return Exception */ public static function driverException(Driver $driver, Throwable $driverEx) { return self::wrapException($driver, $driverEx, 'An exception occurred in driver: ' . $driverEx->getMessage()); } /** * @return Exception */ private static function wrapException(Driver $driver, Throwable $driverEx, string $msg) { if ($driverEx instanceof DriverException) { return $driverEx; } if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DeprecatedDriverException) { return $driver->convertException($msg, $driverEx); } return new Exception($msg, 0, $driverEx); } /** * Returns a human-readable representation of an array of parameters. * This properly handles binary data by returning a hex representation. * * @param mixed[] $params * * @return string */ private static function formatParameters(array $params) { return '[' . implode(', ', array_map(static function ($param) { if (is_resource($param)) { return (string) $param; } $json = @json_encode($param); if (! is_string($json) || $json === 'null' && is_string($param)) { // JSON encoding failed, this is not a UTF-8 string. return sprintf('"%s"', preg_replace('/.{2}/', '\\x$0', bin2hex($param))); } return $json; }, $params)) . ']'; } /** * @param string $wrapperClass * * @return Exception */ public static function invalidWrapperClass($wrapperClass) { return new Exception("The given 'wrapperClass' " . $wrapperClass . ' has to be a ' . 'subtype of \Doctrine\DBAL\Connection.'); } /** * @param string $driverClass * * @return Exception */ public static function invalidDriverClass($driverClass) { return new Exception( "The given 'driverClass' " . $driverClass . ' has to implement the ' . Driver::class . ' interface.' ); } /** * @param string $tableName * * @return Exception */ public static function invalidTableName($tableName) { return new Exception('Invalid table name specified: ' . $tableName); } /** * @param string $tableName * * @return Exception */ public static function noColumnsSpecifiedForTable($tableName) { return new Exception('No columns specified for table ' . $tableName); } /** * @return Exception */ public static function limitOffsetInvalid() { return new Exception('Invalid Offset in Limit Query, it has to be larger than or equal to 0.'); } /** * @param string $name * * @return Exception */ public static function typeExists($name) { return new Exception('Type ' . $name . ' already exists.'); } /** * @param string $name * * @return Exception */ public static function unknownColumnType($name) { return new Exception('Unknown column type "' . $name . '" requested. Any Doctrine type that you use has ' . 'to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the ' . 'known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database ' . 'introspection then you might have forgotten to register all database types for a Doctrine Type. Use ' . 'AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement ' . 'Type#getMappedDatabaseTypes(). If the type name is empty you might ' . 'have a problem with the cache or forgot some mapping information.'); } /** * @param string $name * * @return Exception */ public static function typeNotFound($name) { return new Exception('Type to be overwritten ' . $name . ' does not exist.'); } public static function typeNotRegistered(Type $type): self { return new Exception( sprintf('Type of the class %s@%s is not registered.', get_class($type), spl_object_hash($type)) ); } public static function typeAlreadyRegistered(Type $type): self { return new Exception( sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type)) ); } } dbal/lib/Doctrine/DBAL/Driver.php 0000644 00000003514 15120025742 0012432 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; /** * Driver interface. * Interface that all DBAL drivers must implement. */ interface Driver { /** * Attempts to create a connection with the database. * * The usage of NULL to indicate empty username or password is deprecated. Use an empty string instead. * * @param mixed[] $params All connection parameters passed by the user. * @param string|null $username The username to use when connecting. * @param string|null $password The password to use when connecting. * @param mixed[] $driverOptions The driver options to use when connecting. * * @return \Doctrine\DBAL\Driver\Connection The database connection. */ public function connect(array $params, $username = null, $password = null, array $driverOptions = []); /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return AbstractPlatform The database platform. */ public function getDatabasePlatform(); /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @return AbstractSchemaManager */ public function getSchemaManager(Connection $conn); /** * Gets the name of the driver. * * @deprecated * * @return string The name of the driver. */ public function getName(); /** * Gets the name of the database connected to for this driver. * * @deprecated Use Connection::getDatabase() instead. * * @return string The name of the database. */ public function getDatabase(Connection $conn); } dbal/lib/Doctrine/DBAL/DriverManager.php 0000644 00000041566 15120025742 0013736 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\Common\EventManager; use Doctrine\DBAL\Driver\DrizzlePDOMySql; use Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Mysqli; use Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Driver\PDO; use Doctrine\DBAL\Driver\SQLAnywhere; use Doctrine\DBAL\Driver\SQLSrv; use function array_keys; use function array_merge; use function class_implements; use function in_array; use function is_string; use function is_subclass_of; use function parse_str; use function parse_url; use function preg_replace; use function rawurldecode; use function str_replace; use function strpos; use function substr; /** * Factory for creating {@link Connection} instances. * * @psalm-type OverrideParams = array{ * charset?: string, * dbname?: string, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * password?: string, * path?: string, * pdo?: \PDO, * platform?: Platforms\AbstractPlatform, * port?: int, * user?: string, * unix_socket?: string, * } * @psalm-type Params = array{ * charset?: string, * dbname?: string, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * keepSlave?: bool, * keepReplica?: bool, * master?: OverrideParams, * memory?: bool, * password?: string, * path?: string, * pdo?: \PDO, * platform?: Platforms\AbstractPlatform, * port?: int, * primary?: OverrideParams, * replica?: array<OverrideParams>, * sharding?: array<string,mixed>, * slaves?: array<OverrideParams>, * user?: string, * wrapperClass?: class-string<Connection>, * unix_socket?: string, * } */ final class DriverManager { /** * List of supported drivers and their mappings to the driver classes. * * To add your own driver use the 'driverClass' parameter to {@link DriverManager::getConnection()}. */ private const DRIVER_MAP = [ 'pdo_mysql' => PDO\MySQL\Driver::class, 'pdo_sqlite' => PDO\SQLite\Driver::class, 'pdo_pgsql' => PDO\PgSQL\Driver::class, 'pdo_oci' => PDO\OCI\Driver::class, 'oci8' => OCI8\Driver::class, 'ibm_db2' => IBMDB2\Driver::class, 'pdo_sqlsrv' => PDO\SQLSrv\Driver::class, 'mysqli' => Mysqli\Driver::class, 'drizzle_pdo_mysql' => DrizzlePDOMySql\Driver::class, 'sqlanywhere' => SQLAnywhere\Driver::class, 'sqlsrv' => SQLSrv\Driver::class, ]; /** * List of URL schemes from a database URL and their mappings to driver. * * @var string[] */ private static $driverSchemeAliases = [ 'db2' => 'ibm_db2', 'mssql' => 'pdo_sqlsrv', 'mysql' => 'pdo_mysql', 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason 'postgres' => 'pdo_pgsql', 'postgresql' => 'pdo_pgsql', 'pgsql' => 'pdo_pgsql', 'sqlite' => 'pdo_sqlite', 'sqlite3' => 'pdo_sqlite', ]; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } /** * Creates a connection object based on the specified parameters. * This method returns a Doctrine\DBAL\Connection which wraps the underlying * driver connection. * * $params must contain at least one of the following. * * Either 'driver' with one of the array keys of {@link DRIVER_MAP}, * OR 'driverClass' that contains the full class name (with namespace) of the * driver class to instantiate. * * Other (optional) parameters: * * <b>user (string)</b>: * The username to use when connecting. * * <b>password (string)</b>: * The password to use when connecting. * * <b>driverOptions (array)</b>: * Any additional driver-specific options for the driver. These are just passed * through to the driver. * * <b>pdo</b>: * You can pass an existing PDO instance through this parameter. The PDO * instance will be wrapped in a Doctrine\DBAL\Connection. * This feature is deprecated and no longer supported in 3.0.x version. * * <b>wrapperClass</b>: * You may specify a custom wrapper class through the 'wrapperClass' * parameter but this class MUST inherit from Doctrine\DBAL\Connection. * * <b>driverClass</b>: * The driver class to use. * * @param array<string,mixed> $params * @param Configuration|null $config The configuration to use. * @param EventManager|null $eventManager The event manager to use. * @psalm-param array{ * charset?: string, * dbname?: string, * default_dbname?: string, * driver?: key-of<self::DRIVER_MAP>, * driverClass?: class-string<Driver>, * driverOptions?: array<mixed>, * host?: string, * keepSlave?: bool, * keepReplica?: bool, * master?: OverrideParams, * memory?: bool, * password?: string, * path?: string, * pdo?: \PDO, * platform?: Platforms\AbstractPlatform, * port?: int, * primary?: OverrideParams, * replica?: array<OverrideParams>, * sharding?: array<string,mixed>, * slaves?: array<OverrideParams>, * user?: string, * wrapperClass?: class-string<T>, * } $params * @phpstan-param array<string,mixed> $params * * @psalm-return ($params is array{wrapperClass:mixed} ? T : Connection) * * @throws Exception * * @template T of Connection */ public static function getConnection( array $params, ?Configuration $config = null, ?EventManager $eventManager = null ): Connection { // create default config and event manager, if not set if (! $config) { $config = new Configuration(); } if (! $eventManager) { $eventManager = new EventManager(); } $params = self::parseDatabaseUrl($params); // @todo: deprecated, notice thrown by connection constructor if (isset($params['master'])) { $params['master'] = self::parseDatabaseUrl($params['master']); } // @todo: deprecated, notice thrown by connection constructor if (isset($params['slaves'])) { foreach ($params['slaves'] as $key => $slaveParams) { $params['slaves'][$key] = self::parseDatabaseUrl($slaveParams); } } // URL support for PrimaryReplicaConnection if (isset($params['primary'])) { $params['primary'] = self::parseDatabaseUrl($params['primary']); } if (isset($params['replica'])) { foreach ($params['replica'] as $key => $replicaParams) { $params['replica'][$key] = self::parseDatabaseUrl($replicaParams); } } // URL support for PoolingShardConnection if (isset($params['global'])) { $params['global'] = self::parseDatabaseUrl($params['global']); } if (isset($params['shards'])) { foreach ($params['shards'] as $key => $shardParams) { $params['shards'][$key] = self::parseDatabaseUrl($shardParams); } } // check for existing pdo object if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { throw Exception::invalidPdoInstance(); } if (isset($params['pdo'])) { $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); } $driver = self::createDriver($params); $wrapperClass = Connection::class; if (isset($params['wrapperClass'])) { if (! is_subclass_of($params['wrapperClass'], $wrapperClass)) { throw Exception::invalidWrapperClass($params['wrapperClass']); } /** @var class-string<Connection> $wrapperClass */ $wrapperClass = $params['wrapperClass']; } return new $wrapperClass($params, $driver, $config, $eventManager); } /** * Returns the list of supported drivers. * * @return string[] */ public static function getAvailableDrivers(): array { return array_keys(self::DRIVER_MAP); } /** * @param array<string,mixed> $params * @psalm-param Params $params * @phpstan-param array<string,mixed> $params * * @throws Exception */ private static function createDriver(array $params): Driver { if (isset($params['driverClass'])) { $interfaces = class_implements($params['driverClass'], true); if ($interfaces === false || ! in_array(Driver::class, $interfaces)) { throw Exception::invalidDriverClass($params['driverClass']); } return new $params['driverClass'](); } if (isset($params['driver'])) { if (! isset(self::DRIVER_MAP[$params['driver']])) { throw Exception::unknownDriver($params['driver'], array_keys(self::DRIVER_MAP)); } $class = self::DRIVER_MAP[$params['driver']]; return new $class(); } throw Exception::driverRequired(); } /** * Normalizes the given connection URL path. * * @return string The normalized connection URL path */ private static function normalizeDatabaseUrlPath(string $urlPath): string { // Trim leading slash from URL path. return substr($urlPath, 1); } /** * Extracts parts from a database URL, if present, and returns an * updated list of parameters. * * @param mixed[] $params The list of parameters. * @psalm-param Params $params * @phpstan-param array<string,mixed> $params * * @return mixed[] A modified list of parameters with info from a database * URL extracted into indidivual parameter parts. * @psalm-return Params * @phpstan-return array<string,mixed> * * @throws Exception */ private static function parseDatabaseUrl(array $params): array { if (! isset($params['url'])) { return $params; } // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']); $url = parse_url($url); if ($url === false) { throw new Exception('Malformed parameter "url".'); } foreach ($url as $param => $value) { if (! is_string($value)) { continue; } $url[$param] = rawurldecode($value); } // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any) // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence). unset($params['pdo']); $params = self::parseDatabaseUrlScheme($url['scheme'] ?? null, $params); if (isset($url['host'])) { $params['host'] = $url['host']; } if (isset($url['port'])) { $params['port'] = $url['port']; } if (isset($url['user'])) { $params['user'] = $url['user']; } if (isset($url['pass'])) { $params['password'] = $url['pass']; } $params = self::parseDatabaseUrlPath($url, $params); $params = self::parseDatabaseUrlQuery($url, $params); return $params; } /** * Parses the given connection URL and resolves the given connection parameters. * * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters * via {@link parseDatabaseUrlScheme}. * * @see parseDatabaseUrlScheme * * @param mixed[] $url The URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private static function parseDatabaseUrlPath(array $url, array $params): array { if (! isset($url['path'])) { return $params; } $url['path'] = self::normalizeDatabaseUrlPath($url['path']); // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate // and therefore treat the path as regular DBAL connection URL path. if (! isset($params['driver'])) { return self::parseRegularDatabaseUrlPath($url, $params); } if (strpos($params['driver'], 'sqlite') !== false) { return self::parseSqliteDatabaseUrlPath($url, $params); } return self::parseRegularDatabaseUrlPath($url, $params); } /** * Parses the query part of the given connection URL and resolves the given connection parameters. * * @param mixed[] $url The connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private static function parseDatabaseUrlQuery(array $url, array $params): array { if (! isset($url['query'])) { return $params; } $query = []; parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode return array_merge($params, $query); // parse_str wipes existing array elements } /** * Parses the given regular connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The regular connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private static function parseRegularDatabaseUrlPath(array $url, array $params): array { $params['dbname'] = $url['path']; return $params; } /** * Parses the given SQLite connection URL and resolves the given connection parameters. * * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. * * @see normalizeDatabaseUrlPath * * @param mixed[] $url The SQLite connection URL parts to evaluate. * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. */ private static function parseSqliteDatabaseUrlPath(array $url, array $params): array { if ($url['path'] === ':memory:') { $params['memory'] = true; return $params; } $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key return $params; } /** * Parses the scheme part from given connection URL and resolves the given connection parameters. * * @param string|null $scheme The connection URL scheme, if available * @param mixed[] $params The connection parameters to resolve. * * @return mixed[] The resolved connection parameters. * * @throws Exception If parsing failed or resolution is not possible. */ private static function parseDatabaseUrlScheme($scheme, array $params): array { if ($scheme !== null) { // The requested driver from the URL scheme takes precedence // over the default custom driver from the connection parameters (if any). unset($params['driverClass']); // URL schemes must not contain underscores, but dashes are ok $driver = str_replace('-', '_', $scheme); // The requested driver from the URL scheme takes precedence over the // default driver from the connection parameters. If the driver is // an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql"). // Otherwise, let checkParams decide later if the driver exists. $params['driver'] = self::$driverSchemeAliases[$driver] ?? $driver; return $params; } // If a schemeless connection URL is given, we require a default driver or default custom driver // as connection parameter. if (! isset($params['driverClass']) && ! isset($params['driver'])) { throw Exception::driverRequired($params['url']); } return $params; } } dbal/lib/Doctrine/DBAL/Events.php 0000644 00000002152 15120025742 0012440 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Container for all DBAL events. * * This class cannot be instantiated. */ final class Events { /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } public const postConnect = 'postConnect'; public const onSchemaCreateTable = 'onSchemaCreateTable'; public const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn'; public const onSchemaDropTable = 'onSchemaDropTable'; public const onSchemaAlterTable = 'onSchemaAlterTable'; public const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn'; public const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn'; public const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn'; public const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn'; public const onSchemaColumnDefinition = 'onSchemaColumnDefinition'; public const onSchemaIndexDefinition = 'onSchemaIndexDefinition'; } dbal/lib/Doctrine/DBAL/Exception.php 0000644 00000000147 15120025742 0013134 0 ustar 00 <?php namespace Doctrine\DBAL; /** * @psalm-immutable */ class Exception extends DBALException { } dbal/lib/Doctrine/DBAL/FetchMode.php 0000644 00000003775 15120025742 0013046 0 ustar 00 <?php namespace Doctrine\DBAL; use PDO; /** * Contains statement fetch modes. * * @deprecated Use one of the fetch- or iterate-related methods on the Statement. */ final class FetchMode { /** * Specifies that the fetch method shall return each row as an array indexed * by column name as returned in the corresponding result set. If the result * set contains multiple columns with the same name, the statement returns * only a single value per column name. * * @see \PDO::FETCH_ASSOC */ public const ASSOCIATIVE = PDO::FETCH_ASSOC; /** * Specifies that the fetch method shall return each row as an array indexed * by column number as returned in the corresponding result set, starting at * column 0. * * @see \PDO::FETCH_NUM */ public const NUMERIC = PDO::FETCH_NUM; /** * Specifies that the fetch method shall return each row as an array indexed * by both column name and number as returned in the corresponding result set, * starting at column 0. * * @see \PDO::FETCH_BOTH */ public const MIXED = PDO::FETCH_BOTH; /** * Specifies that the fetch method shall return each row as an object with * property names that correspond to the column names returned in the result * set. * * @see \PDO::FETCH_OBJ */ public const STANDARD_OBJECT = PDO::FETCH_OBJ; /** * Specifies that the fetch method shall return only a single requested * column from the next row in the result set. * * @see \PDO::FETCH_COLUMN */ public const COLUMN = PDO::FETCH_COLUMN; /** * Specifies that the fetch method shall return a new instance of the * requested class, mapping the columns to named properties in the class. * * @see \PDO::FETCH_CLASS */ public const CUSTOM_OBJECT = PDO::FETCH_CLASS; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/LockMode.php 0000644 00000000643 15120025742 0012674 0 ustar 00 <?php namespace Doctrine\DBAL; /** * Contains all DBAL LockModes. */ class LockMode { public const NONE = 0; public const OPTIMISTIC = 1; public const PESSIMISTIC_READ = 2; public const PESSIMISTIC_WRITE = 4; /** * Private constructor. This class cannot be instantiated. * * @codeCoverageIgnore */ final private function __construct() { } } dbal/lib/Doctrine/DBAL/ParameterType.php 0000644 00000002215 15120025742 0013756 0 ustar 00 <?php namespace Doctrine\DBAL; use PDO; /** * Contains statement parameter types. */ final class ParameterType { /** * Represents the SQL NULL data type. * * @see \PDO::PARAM_NULL */ public const NULL = PDO::PARAM_NULL; /** * Represents the SQL INTEGER data type. * * @see \PDO::PARAM_INT */ public const INTEGER = PDO::PARAM_INT; /** * Represents the SQL CHAR, VARCHAR, or other string data type. * * @see \PDO::PARAM_STR */ public const STRING = PDO::PARAM_STR; /** * Represents the SQL large object data type. * * @see \PDO::PARAM_LOB */ public const LARGE_OBJECT = PDO::PARAM_LOB; /** * Represents a boolean data type. * * @see \PDO::PARAM_BOOL */ public const BOOLEAN = PDO::PARAM_BOOL; /** * Represents a binary string data type. */ public const BINARY = 16; /** * Represents an ASCII string data type */ public const ASCII = 17; /** * This class cannot be instantiated. * * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Result.php 0000644 00000002671 15120025742 0012460 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Driver\Exception; use Traversable; /** * This interfaces contains methods allowing forward compatibility with v3.0 Result * * @see https://github.com/doctrine/dbal/blob/3.0.x/src/Result.php */ interface Result extends Abstraction\Result { /** * Returns an array containing the values of the first column of the result. * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(): array; /** * Returns an associative array with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(): array; /** * Returns an iterator over the result set with the values of the first column of the result * * @return Traversable<mixed,mixed> * * @throws Exception */ public function iterateKeyValue(): Traversable; /** * Returns an iterator over the result set with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @return Traversable<mixed,array<string,mixed>> * * @throws Exception */ public function iterateAssociativeIndexed(): Traversable; } dbal/lib/Doctrine/DBAL/SQLParserUtils.php 0000644 00000024302 15120025742 0014032 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Types\Type; use function array_fill; use function array_fill_keys; use function array_key_exists; use function array_keys; use function array_merge; use function array_slice; use function array_values; use function count; use function implode; use function is_int; use function key; use function ksort; use function preg_match_all; use function sprintf; use function strlen; use function strpos; use function substr; use const PREG_OFFSET_CAPTURE; /** * Utility class that parses sql statements with regard to types and parameters. * * @internal */ class SQLParserUtils { /**#@+ * * @deprecated Will be removed as internal implementation details. */ public const POSITIONAL_TOKEN = '\?'; public const NAMED_TOKEN = '(?<!:):[a-zA-Z_][a-zA-Z0-9_]*'; // Quote characters within string literals can be preceded by a backslash. public const ESCAPED_SINGLE_QUOTED_TEXT = "(?:'(?:\\\\)+'|'(?:[^'\\\\]|\\\\'?|'')*')"; public const ESCAPED_DOUBLE_QUOTED_TEXT = '(?:"(?:\\\\)+"|"(?:[^"\\\\]|\\\\"?)*")'; public const ESCAPED_BACKTICK_QUOTED_TEXT = '(?:`(?:\\\\)+`|`(?:[^`\\\\]|\\\\`?)*`)'; /**#@-*/ private const ESCAPED_BRACKET_QUOTED_TEXT = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]'; /** * Gets an array of the placeholders in an sql statements as keys and their positions in the query string. * * For a statement with positional parameters, returns a zero-indexed list of placeholder position. * For a statement with named parameters, returns a map of placeholder positions to their parameter names. * * @deprecated Will be removed as internal implementation detail. * * @param string $statement * @param bool $isPositional * * @return int[]|string[] */ public static function getPlaceholderPositions($statement, $isPositional = true) { return $isPositional ? self::getPositionalPlaceholderPositions($statement) : self::getNamedPlaceholderPositions($statement); } /** * Returns a zero-indexed list of placeholder position. * * @return list<int> */ private static function getPositionalPlaceholderPositions(string $statement): array { return self::collectPlaceholders( $statement, '?', self::POSITIONAL_TOKEN, static function (string $_, int $placeholderPosition, int $fragmentPosition, array &$carry): void { $carry[] = $placeholderPosition + $fragmentPosition; } ); } /** * Returns a map of placeholder positions to their parameter names. * * @return array<int,string> */ private static function getNamedPlaceholderPositions(string $statement): array { return self::collectPlaceholders( $statement, ':', self::NAMED_TOKEN, static function ( string $placeholder, int $placeholderPosition, int $fragmentPosition, array &$carry ): void { $carry[$placeholderPosition + $fragmentPosition] = substr($placeholder, 1); } ); } /** * @return mixed[] */ private static function collectPlaceholders( string $statement, string $match, string $token, callable $collector ): array { if (strpos($statement, $match) === false) { return []; } $carry = []; foreach (self::getUnquotedStatementFragments($statement) as $fragment) { preg_match_all('/' . $token . '/', $fragment[0], $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $placeholder) { $collector($placeholder[0], $placeholder[1], $fragment[1], $carry); } } return $carry; } /** * For a positional query this method can rewrite the sql statement with regard to array parameters. * * @param string $query SQL query * @param mixed[] $params Query parameters * @param array<int, Type|int|string|null>|array<string, Type|int|string|null> $types Parameter types * * @return mixed[] * * @throws SQLParserUtilsException */ public static function expandListParameters($query, $params, $types) { $isPositional = is_int(key($params)); $arrayPositions = []; $bindIndex = -1; if ($isPositional) { // make sure that $types has the same keys as $params // to allow omitting parameters with unspecified types $types += array_fill_keys(array_keys($params), null); ksort($params); ksort($types); } foreach ($types as $name => $type) { ++$bindIndex; if ($type !== Connection::PARAM_INT_ARRAY && $type !== Connection::PARAM_STR_ARRAY) { continue; } if ($isPositional) { $name = $bindIndex; } $arrayPositions[$name] = false; } if (( ! $arrayPositions && $isPositional)) { return [$query, $params, $types]; } if ($isPositional) { $paramOffset = 0; $queryOffset = 0; $params = array_values($params); $types = array_values($types); $paramPos = self::getPositionalPlaceholderPositions($query); foreach ($paramPos as $needle => $needlePos) { if (! isset($arrayPositions[$needle])) { continue; } $needle += $paramOffset; $needlePos += $queryOffset; $count = count($params[$needle]); $params = array_merge( array_slice($params, 0, $needle), $params[$needle], array_slice($params, $needle + 1) ); $types = array_merge( array_slice($types, 0, $needle), $count ? // array needles are at {@link \Doctrine\DBAL\ParameterType} constants // + {@link \Doctrine\DBAL\Connection::ARRAY_PARAM_OFFSET} array_fill(0, $count, $types[$needle] - Connection::ARRAY_PARAM_OFFSET) : [], array_slice($types, $needle + 1) ); $expandStr = $count ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; $query = substr($query, 0, $needlePos) . $expandStr . substr($query, $needlePos + 1); $paramOffset += $count - 1; // Grows larger by number of parameters minus the replaced needle. $queryOffset += strlen($expandStr) - 1; } return [$query, $params, $types]; } $queryOffset = 0; $typesOrd = []; $paramsOrd = []; $paramPos = self::getNamedPlaceholderPositions($query); foreach ($paramPos as $pos => $paramName) { $paramLen = strlen($paramName) + 1; $value = self::extractParam($paramName, $params, true); if (! isset($arrayPositions[$paramName]) && ! isset($arrayPositions[':' . $paramName])) { $pos += $queryOffset; $queryOffset -= $paramLen - 1; $paramsOrd[] = $value; $typesOrd[] = self::extractParam($paramName, $types, false, ParameterType::STRING); $query = substr($query, 0, $pos) . '?' . substr($query, $pos + $paramLen); continue; } $count = count($value); $expandStr = $count > 0 ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; foreach ($value as $val) { $paramsOrd[] = $val; $typesOrd[] = self::extractParam($paramName, $types, false) - Connection::ARRAY_PARAM_OFFSET; } $pos += $queryOffset; $queryOffset += strlen($expandStr) - $paramLen; $query = substr($query, 0, $pos) . $expandStr . substr($query, $pos + $paramLen); } return [$query, $paramsOrd, $typesOrd]; } /** * Slice the SQL statement around pairs of quotes and * return string fragments of SQL outside of quoted literals. * Each fragment is captured as a 2-element array: * * 0 => matched fragment string, * 1 => offset of fragment in $statement * * @param string $statement * * @return mixed[][] */ private static function getUnquotedStatementFragments($statement) { $literal = self::ESCAPED_SINGLE_QUOTED_TEXT . '|' . self::ESCAPED_DOUBLE_QUOTED_TEXT . '|' . self::ESCAPED_BACKTICK_QUOTED_TEXT . '|' . self::ESCAPED_BRACKET_QUOTED_TEXT; $expression = sprintf('/((.+(?i:ARRAY)\\[.+\\])|([^\'"`\\[]+))(?:%s)?/s', $literal); preg_match_all($expression, $statement, $fragments, PREG_OFFSET_CAPTURE); return $fragments[1]; } /** * @param string $paramName The name of the parameter (without a colon in front) * @param mixed $paramsOrTypes A hash of parameters or types * @param bool $isParam * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown * * @return mixed * * @throws SQLParserUtilsException */ private static function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null) { if (array_key_exists($paramName, $paramsOrTypes)) { return $paramsOrTypes[$paramName]; } // Hash keys can be prefixed with a colon for compatibility if (array_key_exists(':' . $paramName, $paramsOrTypes)) { return $paramsOrTypes[':' . $paramName]; } if ($defaultValue !== null) { return $defaultValue; } if ($isParam) { throw SQLParserUtilsException::missingParam($paramName); } throw SQLParserUtilsException::missingType($paramName); } } dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php 0000644 00000001417 15120025742 0015713 0 ustar 00 <?php namespace Doctrine\DBAL; use function sprintf; /** * Doctrine\DBAL\ConnectionException * * @psalm-immutable */ class SQLParserUtilsException extends Exception { /** * @param string $paramName * * @return SQLParserUtilsException */ public static function missingParam($paramName) { return new self( sprintf('Value for :%1$s not found in params array. Params array key should be "%1$s"', $paramName) ); } /** * @param string $typeName * * @return SQLParserUtilsException */ public static function missingType($typeName) { return new self( sprintf('Value for :%1$s not found in types array. Types array key should be "%1$s"', $typeName) ); } } dbal/lib/Doctrine/DBAL/Statement.php 0000644 00000055105 15120025742 0013146 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Abstraction\Result; use Doctrine\DBAL\Driver\Exception; use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\Exception\NoKeyValue; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result as BaseResult; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use IteratorAggregate; use PDO; use PDOStatement; use ReturnTypeWillChange; use Throwable; use Traversable; use function array_shift; use function func_get_args; use function is_array; use function is_string; /** * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support * for logging, DBAL mapping types, etc. */ class Statement implements IteratorAggregate, DriverStatement, Result { /** * The SQL statement. * * @var string */ protected $sql; /** * The bound parameters. * * @var mixed[] */ protected $params = []; /** * The parameter types. * * @var int[]|string[] */ protected $types = []; /** * The underlying driver statement. * * @var \Doctrine\DBAL\Driver\Statement */ protected $stmt; /** * The underlying database platform. * * @var AbstractPlatform */ protected $platform; /** * The connection this statement is bound to and executed on. * * @var Connection */ protected $conn; /** * Creates a new <tt>Statement</tt> for the given SQL and <tt>Connection</tt>. * * @internal The statement can be only instantiated by {@link Connection}. * * @param string $sql The SQL of the statement. * @param Connection $conn The connection on which the statement should be executed. */ public function __construct($sql, Connection $conn) { $this->sql = $sql; $this->stmt = $conn->getWrappedConnection()->prepare($sql); $this->conn = $conn; $this->platform = $conn->getDatabasePlatform(); } /** * Binds a parameter value to the statement. * * The value can optionally be bound with a PDO binding type or a DBAL mapping type. * If bound with a DBAL mapping type, the binding type is derived from the mapping * type and the value undergoes the conversion routines of the mapping type before * being bound. * * @param string|int $param The name or position of the parameter. * @param mixed $value The value of the parameter. * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. * * @return bool TRUE on success, FALSE on failure. */ public function bindValue($param, $value, $type = ParameterType::STRING) { $this->params[$param] = $value; $this->types[$param] = $type; if ($type !== null) { if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->platform); $bindingType = $type->getBindingType(); } else { $bindingType = $type; } return $this->stmt->bindValue($param, $value, $bindingType); } return $this->stmt->bindValue($param, $value); } /** * Binds a parameter to a value by reference. * * Binding a parameter by reference does not support DBAL mapping types. * * @param string|int $param The name or position of the parameter. * @param mixed $variable The reference to the variable to bind. * @param int $type The PDO binding type. * @param int|null $length Must be specified when using an OUT bind * so that PHP allocates enough memory to hold the returned value. * * @return bool TRUE on success, FALSE on failure. */ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) { $this->params[$param] = $variable; $this->types[$param] = $type; if ($this->stmt instanceof PDOStatement) { $length = $length ?? 0; } return $this->stmt->bindParam($param, $variable, $type, $length); } /** * Executes the statement with the currently bound parameters. * * @deprecated Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead * * @param mixed[]|null $params * * @return bool TRUE on success, FALSE on failure. * * @throws Exception */ public function execute($params = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4580', 'Statement::execute() is deprecated, use Statement::executeQuery() or Statement::executeStatement() instead' ); if (is_array($params)) { $this->params = $params; } $logger = $this->conn->getConfiguration()->getSQLLogger(); if ($logger) { $logger->startQuery($this->sql, $this->params, $this->types); } try { $stmt = $this->stmt->execute($params); } catch (Throwable $ex) { if ($logger) { $logger->stopQuery(); } $this->conn->handleExceptionDuringQuery($ex, $this->sql, $this->params, $this->types); } if ($logger) { $logger->stopQuery(); } return $stmt; } /** * Executes the statement with the currently bound parameters and return result. * * @param mixed[] $params * * @throws Exception */ public function executeQuery(array $params = []): BaseResult { if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } $this->execute($params); return new ForwardCompatibility\Result($this); } /** * Executes the statement with the currently bound parameters and return affected rows. * * @param mixed[] $params * * @throws Exception */ public function executeStatement(array $params = []): int { if ($params === []) { $params = null; // Workaround as long execute() exists and used internally. } $this->execute($params); return $this->rowCount(); } /** * Closes the cursor, freeing the database resources used by this statement. * * @deprecated Use Result::free() instead. * * @return bool TRUE on success, FALSE on failure. */ public function closeCursor() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4049', 'Statement::closeCursor() is deprecated, use Result::free() instead.' ); return $this->stmt->closeCursor(); } /** * Returns the number of columns in the result set. * * @return int */ public function columnCount() { return $this->stmt->columnCount(); } /** * Fetches the SQLSTATE associated with the last operation on the statement. * * @deprecated The error information is available via exceptions. * * @return string|int|bool */ public function errorCode() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3507', 'Connection::errorCode() is deprecated, use getCode() or getSQLState() on Exception instead.' ); return $this->stmt->errorCode(); } /** * {@inheritDoc} * * @deprecated The error information is available via exceptions. */ public function errorInfo() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/3507', 'Connection::errorInfo() is deprecated, use getCode() or getSQLState() on Exception instead.' ); return $this->stmt->errorInfo(); } /** * {@inheritdoc} * * @deprecated Use one of the fetch- or iterate-related methods. */ public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Statement::setFetchMode() is deprecated, use explicit Result::fetch*() APIs instead.' ); if ($arg2 === null) { return $this->stmt->setFetchMode($fetchMode); } if ($arg3 === null) { return $this->stmt->setFetchMode($fetchMode, $arg2); } return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3); } /** * Required by interface IteratorAggregate. * * @deprecated Use iterateNumeric(), iterateAssociative() or iterateColumn() instead. * * {@inheritdoc} */ #[ReturnTypeWillChange] public function getIterator() { Deprecation::trigger( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Statement::getIterator() is deprecated, use Result::iterateNumeric(), iterateAssociative() ' . 'or iterateColumn() instead.' ); return $this->stmt; } /** * {@inheritdoc} * * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. */ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Statement::fetch() is deprecated, use Result::fetchNumeric(), fetchAssociative() or fetchOne() instead.' ); return $this->stmt->fetch(...func_get_args()); } /** * {@inheritdoc} * * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Statement::fetchAll() is deprecated, use Result::fetchAllNumeric(), fetchAllAssociative() or ' . 'fetchFirstColumn() instead.' ); if ($ctorArgs !== null) { return $this->stmt->fetchAll($fetchMode, $fetchArgument, $ctorArgs); } if ($fetchArgument !== null) { return $this->stmt->fetchAll($fetchMode, $fetchArgument); } return $this->stmt->fetchAll($fetchMode); } /** * {@inheritDoc} * * @deprecated Use fetchOne() instead. */ public function fetchColumn($columnIndex = 0) { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/pull/4019', 'Statement::fetchColumn() is deprecated, use Result::fetchOne() instead.' ); return $this->stmt->fetchColumn($columnIndex); } /** * {@inheritdoc} * * @deprecated Use Result::fetchNumeric() instead * * @throws Exception */ public function fetchNumeric() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchNumeric(); } return $this->stmt->fetch(FetchMode::NUMERIC); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritdoc} * * @deprecated Use Result::fetchAssociative() instead * * @throws Exception */ public function fetchAssociative() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchAssociative(); } return $this->stmt->fetch(FetchMode::ASSOCIATIVE); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritDoc} * * @deprecated Use Result::fetchOne() instead * * @throws Exception */ public function fetchOne() { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchOne(); } return $this->stmt->fetch(FetchMode::COLUMN); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritdoc} * * @deprecated Use Result::fetchAllNumeric() instead * * @throws Exception */ public function fetchAllNumeric(): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchAllNumeric(); } return $this->stmt->fetchAll(FetchMode::NUMERIC); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritdoc} * * @deprecated Use Result::fetchAllAssociative() instead * * @throws Exception */ public function fetchAllAssociative(): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchAllAssociative(); } return $this->stmt->fetchAll(FetchMode::ASSOCIATIVE); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * Returns an associative array with the keys mapped to the first column and the values mapped to the second column. * * The result must contain at least two columns. * * @deprecated Use Result::fetchAllKeyValue() instead * * @return array<mixed,mixed> * * @throws Exception */ public function fetchAllKeyValue(): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); $this->ensureHasKeyValue(); $data = []; foreach ($this->fetchAllNumeric() as [$key, $value]) { $data[$key] = $value; } return $data; } /** * Returns an associative array with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @deprecated Use Result::fetchAllAssociativeIndexed() instead * * @return array<mixed,array<string,mixed>> * * @throws Exception */ public function fetchAllAssociativeIndexed(): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); $data = []; foreach ($this->fetchAll(FetchMode::ASSOCIATIVE) as $row) { $data[array_shift($row)] = $row; } return $data; } /** * {@inheritdoc} * * @deprecated Use Result::fetchFirstColumn() instead * * @throws Exception */ public function fetchFirstColumn(): array { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { return $this->stmt->fetchFirstColumn(); } return $this->stmt->fetchAll(FetchMode::COLUMN); } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritDoc} * * @deprecated Use Result::iterateNumeric() instead * * @return Traversable<int,array<int,mixed>> * * @throws Exception */ public function iterateNumeric(): Traversable { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { while (($row = $this->stmt->fetchNumeric()) !== false) { yield $row; } } else { while (($row = $this->stmt->fetch(FetchMode::NUMERIC)) !== false) { yield $row; } } } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * {@inheritDoc} * * @deprecated Use Result::iterateAssociative() instead * * @return Traversable<int,array<string,mixed>> * * @throws Exception */ public function iterateAssociative(): Traversable { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { while (($row = $this->stmt->fetchAssociative()) !== false) { yield $row; } } else { while (($row = $this->stmt->fetch(FetchMode::ASSOCIATIVE)) !== false) { yield $row; } } } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * Returns an iterator over the result set with the keys mapped to the first column * and the values mapped to the second column. * * The result must contain at least two columns. * * @deprecated Use Result::iterateKeyValue() instead * * @return Traversable<mixed,mixed> * * @throws Exception */ public function iterateKeyValue(): Traversable { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); $this->ensureHasKeyValue(); foreach ($this->iterateNumeric() as [$key, $value]) { yield $key => $value; } } /** * Returns an iterator over the result set with the keys mapped to the first column and the values being * an associative array representing the rest of the columns and their values. * * @deprecated Use Result::iterateAssociativeIndexed() instead * * @return Traversable<mixed,array<string,mixed>> * * @throws Exception */ public function iterateAssociativeIndexed(): Traversable { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); while (($row = $this->stmt->fetch(FetchMode::ASSOCIATIVE)) !== false) { yield array_shift($row) => $row; } } /** * {@inheritDoc} * * @deprecated Use Result::iterateColumn() instead * * @return Traversable<int,mixed> * * @throws Exception */ public function iterateColumn(): Traversable { Deprecation::triggerIfCalledFromOutside( 'doctrine/dbal', 'https://github.com/doctrine/dbal/issues/4554', 'Statement::%s() is deprecated, use Result::%s() instead.', __FUNCTION__, __FUNCTION__ ); try { if ($this->stmt instanceof Result) { while (($value = $this->stmt->fetchOne()) !== false) { yield $value; } } else { while (($value = $this->stmt->fetch(FetchMode::COLUMN)) !== false) { yield $value; } } } catch (Exception $e) { $this->conn->handleDriverException($e); } } /** * Returns the number of rows affected by the last execution of this statement. * * @return int|string The number of affected rows. */ public function rowCount() { return $this->stmt->rowCount(); } public function free(): void { if ($this->stmt instanceof Result) { $this->stmt->free(); return; } $this->stmt->closeCursor(); } /** * Gets the wrapped driver statement. * * @return \Doctrine\DBAL\Driver\Statement */ public function getWrappedStatement() { return $this->stmt; } private function ensureHasKeyValue(): void { $columnCount = $this->columnCount(); if ($columnCount < 2) { throw NoKeyValue::fromColumnCount($columnCount); } } } dbal/lib/Doctrine/DBAL/TransactionIsolationLevel.php 0000644 00000001145 15120025742 0016334 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\DBAL; final class TransactionIsolationLevel { /** * Transaction isolation level READ UNCOMMITTED. */ public const READ_UNCOMMITTED = 1; /** * Transaction isolation level READ COMMITTED. */ public const READ_COMMITTED = 2; /** * Transaction isolation level REPEATABLE READ. */ public const REPEATABLE_READ = 3; /** * Transaction isolation level SERIALIZABLE. */ public const SERIALIZABLE = 4; /** * @codeCoverageIgnore */ private function __construct() { } } dbal/lib/Doctrine/DBAL/Version.php 0000644 00000001430 15120025742 0012617 0 ustar 00 <?php namespace Doctrine\DBAL; use function str_replace; use function strtoupper; use function version_compare; /** * Class to store and retrieve the version of Doctrine. * * @internal * @deprecated Refrain from checking the DBAL version at runtime. */ class Version { /** * Current Doctrine Version. */ public const VERSION = '2.13.9'; /** * Compares a Doctrine version with the current one. * * @param string $version The Doctrine version to compare to. * * @return int -1 if older, 0 if it is the same, 1 if version passed as argument is newer. */ public static function compare($version) { $version = str_replace(' ', '', strtoupper($version)); return version_compare($version, self::VERSION); } } dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php 0000644 00000001643 15120025742 0016466 0 ustar 00 <?php namespace Doctrine\DBAL; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Contract for a driver that is able to create platform instances by version. * * Doctrine uses different platform classes for different vendor versions to * support the correct features and SQL syntax of each version. * This interface should be implemented by drivers that are capable to do this * distinction. */ interface VersionAwarePlatformDriver { /** * Factory method for creating the appropriate platform instance for the given version. * * @param string $version The platform/server version string to evaluate. This should be given in the notation * the underlying database vendor uses. * * @return AbstractPlatform * * @throws Exception If the given version string could not be evaluated. */ public function createDatabasePlatformForVersion($version); } dbal/CONTRIBUTING.md 0000644 00000000261 15120025742 0007674 0 ustar 00 Doctrine has [general contributing guidelines][contributor workflow], make sure you follow them. [contributor workflow]: https://www.doctrine-project.org/contribute/index.html dbal/LICENSE 0000644 00000002051 15120025742 0006447 0 ustar 00 Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dbal/README.md 0000644 00000005634 15120025742 0006733 0 ustar 00 # Doctrine DBAL | [4.0-dev][4.0] | [3.3][3.3] | [2.13][2.13] | |:----------------:|:----------:|:----------:| | [![GitHub Actions][GA 4.0 image]][GA 4.0] | [![GitHub Actions][GA 3.3 image]][GA 3.3] | [![GitHub Actions][GA 2.13 image]][GA 2.13] | | [![AppVeyor][AppVeyor 4.0 image]][AppVeyor 4.0] | [![AppVeyor][AppVeyor 3.3 image]][AppVeyor 3.3] | [![AppVeyor][AppVeyor 2.13 image]][AppVeyor 2.13] | | [![Code Coverage][Coverage image]][CodeCov 4.0] | [![Code Coverage][Coverage 3.3 image]][CodeCov 3.3] | [![Code Coverage][Coverage 2.13 image]][CodeCov 2.13] | | N/A | [![Code Coverage][TypeCov 3.3 image]][TypeCov 3.3] | N/A | Powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction. ## More resources: * [Website](http://www.doctrine-project.org/projects/dbal.html) * [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/) * [Issue Tracker](https://github.com/doctrine/dbal/issues) [Coverage image]: https://codecov.io/gh/doctrine/dbal/branch/4.0.x/graph/badge.svg [4.0]: https://github.com/doctrine/dbal/tree/4.0.x [CodeCov 4.0]: https://codecov.io/gh/doctrine/dbal/branch/4.0.x [AppVeyor 4.0]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.0.x [AppVeyor 4.0 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.0.x?svg=true [GA 4.0]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x [GA 4.0 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg [Coverage 3.3 image]: https://codecov.io/gh/doctrine/dbal/branch/3.3.x/graph/badge.svg [3.3]: https://github.com/doctrine/dbal/tree/3.3.x [CodeCov 3.3]: https://codecov.io/gh/doctrine/dbal/branch/3.3.x [AppVeyor 3.3]: https://ci.appveyor.com/project/doctrine/dbal/branch/3.3.x [AppVeyor 3.3 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/3.3.x?svg=true [GA 3.3]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.3.x [GA 3.3 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=3.3.x [TypeCov 3.3]: https://shepherd.dev/github/doctrine/dbal [TypeCov 3.3 image]: https://shepherd.dev/github/doctrine/dbal/coverage.svg [Coverage 2.13 image]: https://codecov.io/gh/doctrine/dbal/branch/2.13.x/graph/badge.svg [2.13]: https://github.com/doctrine/dbal/tree/2.13.x [CodeCov 2.13]: https://codecov.io/gh/doctrine/dbal/branch/2.13.x [AppVeyor 2.13]: https://ci.appveyor.com/project/doctrine/dbal/branch/2.13.x [AppVeyor 2.13 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/2.13.x?svg=true [GA 2.13]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A2.13.x [GA 2.13 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=2.13.x dbal/composer.json 0000644 00000004125 15120025742 0010170 0 ustar 00 { "name": "doctrine/dbal", "type": "library", "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", "keywords": [ "abstraction", "database", "dbal", "db2", "mariadb", "mssql", "mysql", "pgsql", "postgresql", "oci8", "oracle", "pdo", "queryobject", "sasql", "sql", "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], "homepage": "https://www.doctrine-project.org/projects/dbal.html", "license": "MIT", "authors": [ {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {"name": "Roman Borschel", "email": "roman@code-factory.org"}, {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, {"name": "Jonathan Wage", "email": "jonwage@gmail.com"} ], "require": { "php": "^7.1 || ^8", "ext-pdo": "*", "doctrine/cache": "^1.0|^2.0", "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1.0" }, "require-dev": { "doctrine/coding-standard": "9.0.0", "jetbrains/phpstorm-stubs": "2021.1", "phpstan/phpstan": "1.4.6", "phpunit/phpunit": "^7.5.20|^8.5|9.5.16", "psalm/plugin-phpunit": "0.16.1", "squizlabs/php_codesniffer": "3.6.2", "symfony/cache": "^4.4", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", "vimeo/psalm": "4.22.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, "bin": ["bin/doctrine-dbal"], "config": { "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "composer/package-versions-deprecated": true } }, "autoload": { "psr-4": { "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" } }, "autoload-dev": { "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } } } doctrine-bundle/Attribute/AsEntityListener.php 0000644 00000000726 15120025742 0015547 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Attribute; use Attribute; /** * Service tag to autoconfigure entity listeners. */ #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] class AsEntityListener { public function __construct( public ?string $event = null, public ?string $method = null, public ?bool $lazy = null, public ?string $entityManager = null, public ?string $entity = null, ) { } } doctrine-bundle/Attribute/AsMiddleware.php 0000644 00000000410 15120025742 0014630 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Attribute; use Attribute; #[Attribute(Attribute::TARGET_CLASS)] class AsMiddleware { /** @param string[] $connections */ public function __construct( public array $connections = [], ) { } } doctrine-bundle/CacheWarmer/DoctrineMetadataCacheWarmer.php 0000644 00000003207 15120025742 0020026 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\CacheWarmer; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; use LogicException; use Symfony\Bundle\FrameworkBundle\CacheWarmer\AbstractPhpFileCacheWarmer; use Symfony\Component\Cache\Adapter\ArrayAdapter; use function is_file; class DoctrineMetadataCacheWarmer extends AbstractPhpFileCacheWarmer { /** @var EntityManagerInterface */ private $entityManager; /** @var string */ private $phpArrayFile; public function __construct(EntityManagerInterface $entityManager, string $phpArrayFile) { $this->entityManager = $entityManager; $this->phpArrayFile = $phpArrayFile; parent::__construct($phpArrayFile); } /** * It must not be optional because it should be called before ProxyCacheWarmer which is not optional. */ public function isOptional(): bool { return false; } /** @param string $cacheDir */ protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter): bool { // cache already warmed up, no needs to do it again if (is_file($this->phpArrayFile)) { return false; } $metadataFactory = $this->entityManager->getMetadataFactory(); if ($metadataFactory instanceof AbstractClassMetadataFactory && $metadataFactory->getLoadedMetadata()) { throw new LogicException('DoctrineMetadataCacheWarmer must load metadata first, check priority of your warmers.'); } $metadataFactory->setCache($arrayAdapter); $metadataFactory->getAllMetadata(); return true; } } doctrine-bundle/Command/Proxy/ClearMetadataCacheDoctrineCommand.php 0000644 00000001421 15120025742 0021430 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear the metadata cache of the various cache drivers. */ class ClearMetadataCacheDoctrineCommand extends MetadataCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-metadata') ->setDescription('Clears all metadata cache for an entity manager'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/ClearQueryCacheDoctrineCommand.php 0000644 00000001377 15120025742 0021027 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear the query cache of the various cache drivers. */ class ClearQueryCacheDoctrineCommand extends QueryCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-query') ->setDescription('Clears all query cache for an entity manager'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/ClearResultCacheDoctrineCommand.php 0000644 00000001401 15120025742 0021164 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear the result cache of the various cache drivers. */ class ClearResultCacheDoctrineCommand extends ResultCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-result') ->setDescription('Clears result cache for an entity manager'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/CollectionRegionDoctrineCommand.php 0000644 00000001302 15120025742 0021252 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\CollectionRegionCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear a collection cache region. */ class CollectionRegionDoctrineCommand extends CollectionRegionCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-collection-region'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/ConvertMappingDoctrineCommand.php 0000644 00000002674 15120025742 0020764 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand; use Doctrine\ORM\Tools\Export\Driver\AbstractExporter; use Doctrine\ORM\Tools\Export\Driver\XmlExporter; use Doctrine\ORM\Tools\Export\Driver\YamlExporter; use Symfony\Component\Console\Input\InputOption; use function assert; /** * Convert Doctrine ORM metadata mapping information between the various supported * formats. */ class ConvertMappingDoctrineCommand extends ConvertMappingCommand { use OrmProxyCommand; /** * {@inheritDoc} */ protected function configure() { parent::configure(); $this ->setName('doctrine:mapping:convert'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } /** * @param string $toType * @param string $destPath * * @return AbstractExporter */ protected function getExporter($toType, $destPath) { $exporter = parent::getExporter($toType, $destPath); assert($exporter instanceof AbstractExporter); if ($exporter instanceof XmlExporter) { $exporter->setExtension('.orm.xml'); } elseif ($exporter instanceof YamlExporter) { $exporter->setExtension('.orm.yml'); } return $exporter; } } doctrine-bundle/Command/Proxy/CreateSchemaDoctrineCommand.php 0000644 00000001462 15120025742 0020346 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to execute the SQL needed to generate the database schema for * a given entity manager. */ class CreateSchemaDoctrineCommand extends CreateCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:schema:create') ->setDescription('Executes (or dumps) the SQL needed to generate the database schema'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/DoctrineCommandHelper.php 0000644 00000004063 15120025742 0017241 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use Symfony\Bundle\FrameworkBundle\Console\Application; use function assert; use function class_exists; use function trigger_deprecation; /** * Provides some helper and convenience methods to configure doctrine commands in the context of bundles * and multiple connections/entity managers. * * @deprecated since DoctrineBundle 2.7 and will be removed in 3.0 */ abstract class DoctrineCommandHelper { /** * Convenience method to push the helper sets of a given entity manager into the application. * * @param string $emName */ public static function setApplicationEntityManager(Application $application, $emName) { $em = $application->getKernel()->getContainer()->get('doctrine')->getManager($emName); assert($em instanceof EntityManagerInterface); $helperSet = $application->getHelperSet(); if (class_exists(ConnectionHelper::class)) { $helperSet->set(new ConnectionHelper($em->getConnection()), 'db'); } $helperSet->set(new EntityManagerHelper($em), 'em'); trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Providing an EntityManager using "%s" is deprecated. Use an instance of "%s" instead.', EntityManagerHelper::class, EntityManagerProvider::class ); } /** * Convenience method to push the helper sets of a given connection into the application. * * @param string $connName */ public static function setApplicationConnection(Application $application, $connName) { $connection = $application->getKernel()->getContainer()->get('doctrine')->getConnection($connName); $helperSet = $application->getHelperSet(); $helperSet->set(new ConnectionHelper($connection), 'db'); } } doctrine-bundle/Command/Proxy/DropSchemaDoctrineCommand.php 0000644 00000001437 15120025742 0020051 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to drop the database schema for a set of classes based on their mappings. */ class DropSchemaDoctrineCommand extends DropCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:schema:drop') ->setDescription('Executes (or dumps) the SQL needed to drop the current database schema'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/EnsureProductionSettingsDoctrineCommand.php 0000644 00000001355 15120025742 0023054 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand; use Symfony\Component\Console\Input\InputOption; /** * Ensure the Doctrine ORM is configured properly for a production environment. */ class EnsureProductionSettingsDoctrineCommand extends EnsureProductionSettingsCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:ensure-production-settings'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/EntityRegionCacheDoctrineCommand.php 0000644 00000001263 15120025742 0021365 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\EntityRegionCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear a entity cache region. */ class EntityRegionCacheDoctrineCommand extends EntityRegionCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-entity-region'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/ImportDoctrineCommand.php 0000644 00000002340 15120025742 0017270 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\DBAL\Tools\Console\Command\ImportCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function trigger_deprecation; /** * Loads an SQL file and executes it. * * @deprecated Use a database client application instead. */ class ImportDoctrineCommand extends ImportCommand { /** * {@inheritDoc} */ protected function configure() { parent::configure(); $this ->setName('doctrine:database:import') ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); } protected function execute(InputInterface $input, OutputInterface $output): int { trigger_deprecation( 'doctrine/doctrine-bundle', '2.2', 'The "%s" (doctrine:database:import) is deprecated, use a database client instead.', self::class ); DoctrineCommandHelper::setApplicationConnection($this->getApplication(), $input->getOption('connection')); return parent::execute($input, $output); } } doctrine-bundle/Command/Proxy/InfoDoctrineCommand.php 0000644 00000001137 15120025742 0016714 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\InfoCommand; use Symfony\Component\Console\Input\InputOption; /** * Show information about mapped entities */ class InfoDoctrineCommand extends InfoCommand { use OrmProxyCommand; protected function configure(): void { $this ->setName('doctrine:mapping:info'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/OrmProxyCommand.php 0000644 00000001557 15120025742 0016136 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\EntityManagerProvider; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** @internal */ trait OrmProxyCommand { /** @var EntityManagerProvider|null */ private $entityManagerProvider; public function __construct(?EntityManagerProvider $entityManagerProvider = null) { parent::__construct($entityManagerProvider); $this->entityManagerProvider = $entityManagerProvider; } protected function execute(InputInterface $input, OutputInterface $output): int { if (! $this->entityManagerProvider) { DoctrineCommandHelper::setApplicationEntityManager($this->getApplication(), $input->getOption('em')); } return parent::execute($input, $output); } } doctrine-bundle/Command/Proxy/QueryRegionCacheDoctrineCommand.php 0000644 00000001256 15120025742 0021220 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryRegionCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to clear a query cache region. */ class QueryRegionCacheDoctrineCommand extends QueryRegionCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:cache:clear-query-region'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/RunDqlDoctrineCommand.php 0000644 00000002352 15120025742 0017226 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\RunDqlCommand; use Symfony\Component\Console\Input\InputOption; /** * Execute a Doctrine DQL query and output the results. */ class RunDqlDoctrineCommand extends RunDqlCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:query:dql') ->setHelp(<<<EOT The <info>%command.name%</info> command executes the given DQL query and outputs the results: <info>php %command.full_name% "SELECT u FROM UserBundle:User u"</info> You can also optional specify some additional options like what type of hydration to use when executing the query: <info>php %command.full_name% "SELECT u FROM UserBundle:User u" --hydrate=array</info> Additionally you can specify the first result and maximum amount of results to show: <info>php %command.full_name% "SELECT u FROM UserBundle:User u" --first-result=0 --max-result=30</info> EOT ); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/RunSqlDoctrineCommand.php 0000644 00000004135 15120025742 0017246 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function trigger_deprecation; /** * Execute a SQL query and output the results. * * @deprecated use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand instead */ class RunSqlDoctrineCommand extends RunSqlCommand { /** @var ConnectionProvider|null */ private $connectionProvider; public function __construct(?ConnectionProvider $connectionProvider = null) { parent::__construct($connectionProvider); $this->connectionProvider = $connectionProvider; } protected function configure(): void { parent::configure(); $this ->setName('doctrine:query:sql') ->setHelp(<<<EOT The <info>%command.name%</info> command executes the given SQL query and outputs the results: <info>php %command.full_name% "SELECT * FROM users"</info> EOT ); if ($this->getDefinition()->hasOption('connection')) { return; } $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); } protected function execute(InputInterface $input, OutputInterface $output): int { trigger_deprecation( 'doctrine/doctrine-bundle', '2.2', 'The "%s" (doctrine:query:sql) is deprecated, use dbal:run-sql command instead.', self::class ); if (! $this->connectionProvider) { DoctrineCommandHelper::setApplicationConnection($this->getApplication(), $input->getOption('connection')); // compatibility with doctrine/dbal 2.11+ // where this option is also present and unsupported before we are not switching to use a ConnectionProvider $input->setOption('connection', null); } return parent::execute($input, $output); } } doctrine-bundle/Command/Proxy/UpdateSchemaDoctrineCommand.php 0000644 00000001334 15120025742 0020363 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to generate the SQL needed to update the database schema to match * the current mapping information. */ class UpdateSchemaDoctrineCommand extends UpdateCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:schema:update'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/Proxy/ValidateSchemaCommand.php 0000644 00000001332 15120025742 0017200 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command\Proxy; use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand as DoctrineValidateSchemaCommand; use Symfony\Component\Console\Input\InputOption; /** * Command to run Doctrine ValidateSchema() on the current mappings. */ class ValidateSchemaCommand extends DoctrineValidateSchemaCommand { use OrmProxyCommand; protected function configure(): void { parent::configure(); $this ->setName('doctrine:schema:validate'); if ($this->getDefinition()->hasOption('em')) { return; } $this->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command'); } } doctrine-bundle/Command/CreateDatabaseDoctrineCommand.php 0000644 00000012577 15120025742 0017542 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Sharding\PoolingShardConnection; use InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; use function array_merge; use function in_array; use function sprintf; use function trigger_deprecation; /** * Database tool allows you to easily create your configured databases. * * @final */ class CreateDatabaseDoctrineCommand extends DoctrineCommand { protected function configure(): void { $this ->setName('doctrine:database:create') ->setDescription('Creates the configured database') ->addOption('shard', 's', InputOption::VALUE_REQUIRED, 'The shard connection to use for this command') ->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command') ->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists') ->setHelp(<<<EOT The <info>%command.name%</info> command creates the default connections database: <info>php %command.full_name%</info> You can also optionally specify the name of a connection to create the database for: <info>php %command.full_name% --connection=default</info> EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $connectionName = $input->getOption('connection'); if (empty($connectionName)) { $connectionName = $this->getDoctrine()->getDefaultConnectionName(); } $connection = $this->getDoctrineConnection($connectionName); $ifNotExists = $input->getOption('if-not-exists'); $params = $connection->getParams(); if (isset($params['primary'])) { $params = $params['primary']; } // Cannot inject `shard` option in parent::getDoctrineConnection // cause it will try to connect to a non-existing database if (isset($params['shards'])) { $shards = $params['shards']; // Default select global $params = array_merge($params, $params['global'] ?? []); unset($params['global']['dbname'], $params['global']['path'], $params['global']['url']); if ($input->getOption('shard')) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Passing a "shard" option for "%s" is deprecated. DBAL 3 does not support shards anymore.', self::class ); foreach ($shards as $i => $shard) { if ($shard['id'] === (int) $input->getOption('shard')) { // Select sharded database $params = array_merge($params, $shard); unset($params['shards'][$i]['dbname'], $params['shards'][$i]['path'], $params['shards'][$i]['url'], $params['id']); break; } } } } $hasPath = isset($params['path']); $name = $hasPath ? $params['path'] : ($params['dbname'] ?? false); if (! $name) { throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be created."); } // Need to get rid of _every_ occurrence of dbname from connection configuration and we have already extracted all relevant info from url unset($params['dbname'], $params['path'], $params['url']); $tmpConnection = DriverManager::getConnection($params); if ($tmpConnection instanceof PoolingShardConnection) { $tmpConnection->connect($input->getOption('shard')); trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Using a DBAL connection of type "%s" is deprecated. DBAL 3 does not support shards anymore.', PoolingShardConnection::class ); } else { $tmpConnection->connect(); } $shouldNotCreateDatabase = $ifNotExists && in_array($name, $tmpConnection->getSchemaManager()->listDatabases()); // Only quote if we don't have a path if (! $hasPath) { $name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name); } $error = false; try { if ($shouldNotCreateDatabase) { $output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> already exists. Skipped.</info>', $name, $connectionName)); } else { $tmpConnection->getSchemaManager()->createDatabase($name); $output->writeln(sprintf('<info>Created database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName)); } } catch (Throwable $e) { $output->writeln(sprintf('<error>Could not create database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName)); $output->writeln(sprintf('<error>%s</error>', $e->getMessage())); $error = true; } $tmpConnection->close(); return $error ? 1 : 0; } } doctrine-bundle/Command/DoctrineCommand.php 0000644 00000005450 15120025742 0014761 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Sharding\PoolingShardConnection; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\EntityGenerator; use Doctrine\Persistence\ManagerRegistry; use LogicException; use Symfony\Component\Console\Command\Command; use function sprintf; use function trigger_deprecation; /** * Base class for Doctrine console commands to extend from. * * @internal */ abstract class DoctrineCommand extends Command { /** @var ManagerRegistry */ private $doctrine; public function __construct(ManagerRegistry $doctrine) { parent::__construct(); $this->doctrine = $doctrine; } /** * get a doctrine entity generator * * @return EntityGenerator */ protected function getEntityGenerator() { $entityGenerator = new EntityGenerator(); $entityGenerator->setGenerateAnnotations(false); $entityGenerator->setGenerateStubMethods(true); $entityGenerator->setRegenerateEntityIfExists(false); $entityGenerator->setUpdateEntityIfExists(true); $entityGenerator->setNumSpaces(4); $entityGenerator->setAnnotationPrefix('ORM\\'); return $entityGenerator; } /** * Get a doctrine entity manager by symfony name. * * @param string $name * @param int|null $shardId * * @return EntityManager */ protected function getEntityManager($name, $shardId = null) { $manager = $this->getDoctrine()->getManager($name); if ($shardId) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Passing a "shardId" argument to "%s" is deprecated. DBAL 3 does not support shards anymore.', __METHOD__ ); if (! $manager instanceof EntityManagerInterface) { throw new LogicException(sprintf('Sharding is supported only in EntityManager of instance "%s".', EntityManagerInterface::class)); } $connection = $manager->getConnection(); if (! $connection instanceof PoolingShardConnection) { throw new LogicException(sprintf("Connection of EntityManager '%s' must implement shards configuration.", $name)); } $connection->connect($shardId); } return $manager; } /** * Get a doctrine dbal connection by symfony name. * * @param string $name * * @return Connection */ protected function getDoctrineConnection($name) { return $this->getDoctrine()->getConnection($name); } /** @return ManagerRegistry */ protected function getDoctrine() { return $this->doctrine; } } doctrine-bundle/Command/DropDatabaseDoctrineCommand.php 0000644 00000012600 15120025742 0017226 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command; use Doctrine\DBAL\DriverManager; use InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Throwable; use function array_merge; use function in_array; use function sprintf; use function trigger_deprecation; /** * Database tool allows you to easily drop your configured databases. * * @final */ class DropDatabaseDoctrineCommand extends DoctrineCommand { public const RETURN_CODE_NOT_DROP = 1; public const RETURN_CODE_NO_FORCE = 2; /** * {@inheritDoc} */ protected function configure() { $this ->setName('doctrine:database:drop') ->setDescription('Drops the configured database') ->addOption('shard', 's', InputOption::VALUE_REQUIRED, 'The shard connection to use for this command') ->addOption('connection', 'c', InputOption::VALUE_OPTIONAL, 'The connection to use for this command') ->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Set this parameter to execute this action') ->setHelp(<<<EOT The <info>%command.name%</info> command drops the default connections database: <info>php %command.full_name%</info> The <info>--force</info> parameter has to be used to actually drop the database. You can also optionally specify the name of a connection to drop the database for: <info>php %command.full_name% --connection=default</info> <error>Be careful: All data in a given database will be lost when executing this command.</error> EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $connectionName = $input->getOption('connection'); if (empty($connectionName)) { $connectionName = $this->getDoctrine()->getDefaultConnectionName(); } $connection = $this->getDoctrineConnection($connectionName); $ifExists = $input->getOption('if-exists'); $params = $connection->getParams(); if (isset($params['primary'])) { $params = $params['primary']; } if (isset($params['shards'])) { $shards = $params['shards']; // Default select global $params = array_merge($params, $params['global'] ?? []); if ($input->getOption('shard')) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Passing a "shard" option for "%s" is deprecated. DBAL 3 does not support shards anymore.', self::class ); foreach ($shards as $shard) { if ($shard['id'] === (int) $input->getOption('shard')) { // Select sharded database $params = array_merge($params, $shard); unset($params['id']); break; } } } } $name = $params['path'] ?? ($params['dbname'] ?? false); if (! $name) { throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped."); } unset($params['dbname'], $params['url']); if (! $input->getOption('force')) { $output->writeln('<error>ATTENTION:</error> This operation should not be executed in a production environment.'); $output->writeln(''); $output->writeln(sprintf('<info>Would drop the database <comment>%s</comment> for connection named <comment>%s</comment>.</info>', $name, $connectionName)); $output->writeln('Please run the operation with --force to execute'); $output->writeln('<error>All data will be lost!</error>'); return self::RETURN_CODE_NO_FORCE; } // Reopen connection without database name set // as some vendors do not allow dropping the database connected to. $connection->close(); $connection = DriverManager::getConnection($params); $shouldDropDatabase = ! $ifExists || in_array($name, $connection->getSchemaManager()->listDatabases()); // Only quote if we don't have a path if (! isset($params['path'])) { $name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name); } try { if ($shouldDropDatabase) { $connection->getSchemaManager()->dropDatabase($name); $output->writeln(sprintf('<info>Dropped database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName)); } else { $output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> doesn\'t exist. Skipped.</info>', $name, $connectionName)); } return 0; } catch (Throwable $e) { $output->writeln(sprintf('<error>Could not drop database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName)); $output->writeln(sprintf('<error>%s</error>', $e->getMessage())); return self::RETURN_CODE_NOT_DROP; } } } doctrine-bundle/Command/ImportMappingDoctrineCommand.php 0000644 00000015414 15120025742 0017471 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Command; use Doctrine\ORM\Mapping\Driver\DatabaseDriver; use Doctrine\ORM\Tools\Console\MetadataFilter; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\Persistence\ManagerRegistry; use InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function chmod; use function dirname; use function file_put_contents; use function is_dir; use function mkdir; use function sprintf; use function str_replace; use function trigger_deprecation; /** * Import Doctrine ORM metadata mapping information from an existing database. * * @deprecated * * @final */ class ImportMappingDoctrineCommand extends DoctrineCommand { /** @var string[] */ private $bundles; /** @param string[] $bundles */ public function __construct(ManagerRegistry $doctrine, array $bundles) { parent::__construct($doctrine); $this->bundles = $bundles; } protected function configure(): void { $this ->setName('doctrine:mapping:import') ->addArgument('name', InputArgument::REQUIRED, 'The bundle or namespace to import the mapping information to') ->addArgument('mapping-type', InputArgument::OPTIONAL, 'The mapping type to export the imported mapping information to') ->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager to use for this command') ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command') ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A string pattern used to match entities that should be mapped.') ->addOption('force', null, InputOption::VALUE_NONE, 'Force to overwrite existing mapping files.') ->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path where the files would be generated (not used when a bundle is passed).') ->setDescription('Imports mapping information from an existing database') ->setHelp(<<<EOT The <info>%command.name%</info> command imports mapping information from an existing database: Generate annotation mappings into the src/ directory using App as the namespace: <info>php %command.full_name% App\\\Entity annotation --path=src/Entity</info> Generate xml mappings into the config/doctrine/ directory using App as the namespace: <info>php %command.full_name% App\\\Entity xml --path=config/doctrine</info> Generate XML mappings into a bundle: <info>php %command.full_name% "MyCustomBundle" xml</info> You can also optionally specify which entity manager to import from with the <info>--em</info> option: <info>php %command.full_name% "MyCustomBundle" xml --em=default</info> If you don't want to map every entity that can be found in the database, use the <info>--filter</info> option. It will try to match the targeted mapped entity with the provided pattern string. <info>php %command.full_name% "MyCustomBundle" xml --filter=MyMatchedEntity</info> Use the <info>--force</info> option, if you want to override existing mapping files: <info>php %command.full_name% "MyCustomBundle" xml --force</info> EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $type = $input->getArgument('mapping-type') ?: 'xml'; if ($type === 'yaml') { $type = 'yml'; } $namespaceOrBundle = $input->getArgument('name'); if (isset($this->bundles[$namespaceOrBundle])) { $bundle = $this->getApplication()->getKernel()->getBundle($namespaceOrBundle); $namespace = $bundle->getNamespace() . '\Entity'; $destPath = $bundle->getPath(); if ($type === 'annotation') { $destPath .= '/Entity'; } else { $destPath .= '/Resources/config/doctrine'; } } else { // assume a namespace has been passed $namespace = $namespaceOrBundle; $destPath = $input->getOption('path'); if ($destPath === null) { throw new InvalidArgumentException('The --path option is required when passing a namespace (e.g. --path=src). If you intended to pass a bundle name, check your spelling.'); } } $cme = new ClassMetadataExporter(); $exporter = $cme->getExporter($type); $exporter->setOverwriteExistingFiles($input->getOption('force')); if ($type === 'annotation') { $entityGenerator = $this->getEntityGenerator(); $exporter->setEntityGenerator($entityGenerator); } if ($input->getOption('shard')) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Passing a "shard" option for "%s" is deprecated. DBAL 3 does not support shards anymore.', self::class ); } $em = $this->getEntityManager($input->getOption('em'), $input->getOption('shard')); $databaseDriver = new DatabaseDriver($em->getConnection()->getSchemaManager()); $em->getConfiguration()->setMetadataDriverImpl($databaseDriver); $emName = $input->getOption('em'); $emName = $emName ? $emName : 'default'; $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); $metadata = $cmf->getAllMetadata(); $metadata = MetadataFilter::filter($metadata, $input->getOption('filter')); if ($metadata) { $output->writeln(sprintf('Importing mapping information from "<info>%s</info>" entity manager', $emName)); foreach ($metadata as $class) { $className = $class->name; $class->name = $namespace . '\\' . $className; if ($type === 'annotation') { $path = $destPath . '/' . str_replace('\\', '.', $className) . '.php'; } else { $path = $destPath . '/' . str_replace('\\', '.', $className) . '.orm.' . $type; } $output->writeln(sprintf(' > writing <comment>%s</comment>', $path)); $code = $exporter->exportClassMetadata($class); $dir = dirname($path); if (! is_dir($dir)) { mkdir($dir, 0775, true); } file_put_contents($path, $code); chmod($path, 0664); } return 0; } $output->writeln('Database does not have any mapping information.'); $output->writeln(''); return 1; } } doctrine-bundle/Controller/ProfilerController.php 0000644 00000012353 15120025742 0016306 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Controller; use Doctrine\Bundle\DoctrineBundle\Registry; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ForwardCompatibility\Result; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use LogicException; use PDO; use PDOStatement; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\VarDumper\Cloner\Data; use Throwable; use Twig\Environment; use function assert; use function stripos; /** @internal */ class ProfilerController { /** @var Environment */ private $twig; /** @var Registry */ private $registry; /** @var Profiler */ private $profiler; public function __construct(Environment $twig, Registry $registry, Profiler $profiler) { $this->twig = $twig; $this->registry = $registry; $this->profiler = $profiler; } /** * Renders the profiler panel for the given token. * * @param string $token The profiler token * @param string $connectionName * @param int $query * * @return Response A Response instance */ public function explainAction($token, $connectionName, $query) { $this->profiler->disable(); $profile = $this->profiler->loadProfile($token); $collector = $profile->getCollector('db'); assert($collector instanceof DoctrineDataCollector); $queries = $collector->getQueries(); if (! isset($queries[$connectionName][$query])) { return new Response('This query does not exist.'); } $query = $queries[$connectionName][$query]; if (! $query['explainable']) { return new Response('This query cannot be explained.'); } $connection = $this->registry->getConnection($connectionName); assert($connection instanceof Connection); try { $platform = $connection->getDatabasePlatform(); if ($platform instanceof SqlitePlatform) { $results = $this->explainSQLitePlatform($connection, $query); } elseif ($platform instanceof SQLServerPlatform) { $results = $this->explainSQLServerPlatform($connection, $query); } elseif ($platform instanceof OraclePlatform) { $results = $this->explainOraclePlatform($connection, $query); } else { $results = $this->explainOtherPlatform($connection, $query); } } catch (Throwable $e) { return new Response('This query cannot be explained.'); } return new Response($this->twig->render('@Doctrine/Collector/explain.html.twig', [ 'data' => $results, 'query' => $query, ])); } /** * @param mixed[] $query * * @return mixed[] */ private function explainSQLitePlatform(Connection $connection, array $query): array { $params = $query['params']; if ($params instanceof Data) { $params = $params->getValue(true); } return $connection->executeQuery('EXPLAIN QUERY PLAN ' . $query['sql'], $params, $query['types']) ->fetchAll(PDO::FETCH_ASSOC); } /** * @param mixed[] $query * * @return mixed[] */ private function explainSQLServerPlatform(Connection $connection, array $query): array { if (stripos($query['sql'], 'SELECT') === 0) { $sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;'; } else { $sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] . '; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;'; } $params = $query['params']; if ($params instanceof Data) { $params = $params->getValue(true); } $stmt = $connection->executeQuery($sql, $params, $query['types']); // DBAL 2.13 "forward compatibility" BC break handling if ($stmt instanceof Result) { $stmt = $stmt->getIterator(); } if (! $stmt instanceof PDOStatement) { throw new LogicException('We need nextRowSet() functionality feature, which is not available with current DBAL driver'); } $stmt->nextRowset(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } /** * @param mixed[] $query * * @return mixed[] */ private function explainOtherPlatform(Connection $connection, array $query): array { $params = $query['params']; if ($params instanceof Data) { $params = $params->getValue(true); } return $connection->executeQuery('EXPLAIN ' . $query['sql'], $params, $query['types']) ->fetchAll(PDO::FETCH_ASSOC); } /** * @param mixed[] $query * * @return mixed[] */ private function explainOraclePlatform(Connection $connection, array $query): array { $connection->executeQuery('EXPLAIN PLAN FOR ' . $query['sql']); return $connection->executeQuery('SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY())') ->fetchAll(PDO::FETCH_ASSOC); } } doctrine-bundle/DataCollector/DoctrineDataCollector.php 0000644 00000024160 15120025742 0017264 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DataCollector; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Cache\CacheConfiguration; use Doctrine\ORM\Cache\Logging\CacheLoggerChain; use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\SchemaValidator; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector as BaseCollector; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Throwable; use function array_map; use function array_sum; use function assert; use function count; use function usort; /** * @psalm-type QueryType = array{ * executionMS: float, * explainable: bool, * sql: string, * params: ?array<array-key, mixed>, * runnable: bool, * types: ?array<array-key, Type|int|string|null>, * } * @psalm-type DataType = array{ * caches: array{ * enabled: bool, * counts: array<"puts"|"hits"|"misses", int>, * log_enabled: bool, * regions: array<"puts"|"hits"|"misses", array<string, int>>, * }, * connections: list<string>, * entities: array<string, array<class-string, class-string>>, * errors: array<string, array<class-string, list<string>>>, * managers: list<string>, * queries: array<string, list<QueryType>>, * } * @psalm-property DataType $data */ class DoctrineDataCollector extends BaseCollector { /** @var ManagerRegistry */ private $registry; /** @var int|null */ private $invalidEntityCount; /** * @var mixed[][] * @psalm-var ?array<string, list<QueryType&array{count: int, index: int, executionPercent: float}>> */ private $groupedQueries; /** @var bool */ private $shouldValidateSchema; /** @psalm-suppress UndefinedClass */ public function __construct(ManagerRegistry $registry, bool $shouldValidateSchema = true, ?DebugDataHolder $debugDataHolder = null) { $this->registry = $registry; $this->shouldValidateSchema = $shouldValidateSchema; if ($debugDataHolder === null) { parent::__construct($registry); } else { /** @psalm-suppress TooManyArguments */ parent::__construct($registry, $debugDataHolder); } } /** * {@inheritdoc} */ public function collect(Request $request, Response $response, ?Throwable $exception = null) { parent::collect($request, $response, $exception); $errors = []; $entities = []; $caches = [ 'enabled' => false, 'log_enabled' => false, 'counts' => [ 'puts' => 0, 'hits' => 0, 'misses' => 0, ], 'regions' => [ 'puts' => [], 'hits' => [], 'misses' => [], ], ]; foreach ($this->registry->getManagers() as $name => $em) { assert($em instanceof EntityManagerInterface); if ($this->shouldValidateSchema) { $entities[$name] = []; $factory = $em->getMetadataFactory(); $validator = new SchemaValidator($em); assert($factory instanceof AbstractClassMetadataFactory); foreach ($factory->getLoadedMetadata() as $class) { assert($class instanceof ClassMetadataInfo); if (isset($entities[$name][$class->getName()])) { continue; } $classErrors = $validator->validateClass($class); $entities[$name][$class->getName()] = $class->getName(); if (empty($classErrors)) { continue; } $errors[$name][$class->getName()] = $classErrors; } } $emConfig = $em->getConfiguration(); assert($emConfig instanceof Configuration); $slcEnabled = $emConfig->isSecondLevelCacheEnabled(); if (! $slcEnabled) { continue; } $caches['enabled'] = true; $cacheConfiguration = $emConfig->getSecondLevelCacheConfiguration(); assert($cacheConfiguration instanceof CacheConfiguration); $cacheLoggerChain = $cacheConfiguration->getCacheLogger(); assert($cacheLoggerChain instanceof CacheLoggerChain || $cacheLoggerChain === null); if (! $cacheLoggerChain || ! $cacheLoggerChain->getLogger('statistics')) { continue; } $cacheLoggerStats = $cacheLoggerChain->getLogger('statistics'); assert($cacheLoggerStats instanceof StatisticsCacheLogger); $caches['log_enabled'] = true; $caches['counts']['puts'] += $cacheLoggerStats->getPutCount(); $caches['counts']['hits'] += $cacheLoggerStats->getHitCount(); $caches['counts']['misses'] += $cacheLoggerStats->getMissCount(); foreach ($cacheLoggerStats->getRegionsPut() as $key => $value) { if (! isset($caches['regions']['puts'][$key])) { $caches['regions']['puts'][$key] = 0; } $caches['regions']['puts'][$key] += $value; } foreach ($cacheLoggerStats->getRegionsHit() as $key => $value) { if (! isset($caches['regions']['hits'][$key])) { $caches['regions']['hits'][$key] = 0; } $caches['regions']['hits'][$key] += $value; } foreach ($cacheLoggerStats->getRegionsMiss() as $key => $value) { if (! isset($caches['regions']['misses'][$key])) { $caches['regions']['misses'][$key] = 0; } $caches['regions']['misses'][$key] += $value; } } $this->data['entities'] = $entities; $this->data['errors'] = $errors; $this->data['caches'] = $caches; $this->groupedQueries = null; } /** @return array<string, array<string, string>> */ public function getEntities() { return $this->data['entities']; } /** @return array<string, array<string, list<string>>> */ public function getMappingErrors() { return $this->data['errors']; } /** @return int */ public function getCacheHitsCount() { return $this->data['caches']['counts']['hits']; } /** @return int */ public function getCachePutsCount() { return $this->data['caches']['counts']['puts']; } /** @return int */ public function getCacheMissesCount() { return $this->data['caches']['counts']['misses']; } /** @return bool */ public function getCacheEnabled() { return $this->data['caches']['enabled']; } /** * @return array<string, array<string, int>> * @psalm-return array<"puts"|"hits"|"misses", array<string, int>> */ public function getCacheRegions() { return $this->data['caches']['regions']; } /** @return array<string, int> */ public function getCacheCounts() { return $this->data['caches']['counts']; } /** @return int */ public function getInvalidEntityCount() { if ($this->invalidEntityCount === null) { $this->invalidEntityCount = array_sum(array_map('count', $this->data['errors'])); } return $this->invalidEntityCount; } /** * @return string[][] * @psalm-return array<string, list<QueryType&array{count: int, index: int, executionPercent: float}>> */ public function getGroupedQueries() { if ($this->groupedQueries !== null) { return $this->groupedQueries; } $this->groupedQueries = []; $totalExecutionMS = 0; foreach ($this->data['queries'] as $connection => $queries) { $connectionGroupedQueries = []; foreach ($queries as $i => $query) { $key = $query['sql']; if (! isset($connectionGroupedQueries[$key])) { $connectionGroupedQueries[$key] = $query; $connectionGroupedQueries[$key]['executionMS'] = 0; $connectionGroupedQueries[$key]['count'] = 0; $connectionGroupedQueries[$key]['index'] = $i; // "Explain query" relies on query index in 'queries'. } $connectionGroupedQueries[$key]['executionMS'] += $query['executionMS']; $connectionGroupedQueries[$key]['count']++; $totalExecutionMS += $query['executionMS']; } usort($connectionGroupedQueries, static function ($a, $b) { if ($a['executionMS'] === $b['executionMS']) { return 0; } return $a['executionMS'] < $b['executionMS'] ? 1 : -1; }); $this->groupedQueries[$connection] = $connectionGroupedQueries; } foreach ($this->groupedQueries as $connection => $queries) { foreach ($queries as $i => $query) { $this->groupedQueries[$connection][$i]['executionPercent'] = $this->executionTimePercentage($query['executionMS'], $totalExecutionMS); } } return $this->groupedQueries; } private function executionTimePercentage(float $executionTimeMS, float $totalExecutionTimeMS): float { if (! $totalExecutionTimeMS) { return 0; } return $executionTimeMS / $totalExecutionTimeMS * 100; } /** @return int */ public function getGroupedQueryCount() { $count = 0; foreach ($this->getGroupedQueries() as $connectionGroupedQueries) { $count += count($connectionGroupedQueries); } return $count; } } doctrine-bundle/Dbal/Logging/BacktraceLogger.php 0000644 00000001237 15120025742 0015603 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Dbal\Logging; use Doctrine\DBAL\Logging\DebugStack; use function array_shift; use function debug_backtrace; use const DEBUG_BACKTRACE_IGNORE_ARGS; final class BacktraceLogger extends DebugStack { /** * {@inheritdoc} */ public function startQuery($sql, ?array $params = null, ?array $types = null): void { parent::startQuery($sql, $params, $types); $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); // skip first since it's always the current method array_shift($backtrace); $this->queries[$this->currentQuery]['backtrace'] = $backtrace; } } doctrine-bundle/Dbal/BlacklistSchemaAssetFilter.php 0000644 00000001265 15120025742 0016376 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Dbal; use Doctrine\DBAL\Schema\AbstractAsset; use function in_array; /** @deprecated Implement your own include/exclude mechanism */ class BlacklistSchemaAssetFilter { /** @var string[] */ private $blacklist; /** @param string[] $blacklist */ public function __construct(array $blacklist) { $this->blacklist = $blacklist; } /** @param string|AbstractAsset $assetName */ public function __invoke($assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return ! in_array($assetName, $this->blacklist, true); } } doctrine-bundle/Dbal/ManagerRegistryAwareConnectionProvider.php 0000644 00000001335 15120025742 0021013 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Dbal; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\Persistence\AbstractManagerRegistry; class ManagerRegistryAwareConnectionProvider implements ConnectionProvider { /** @var AbstractManagerRegistry */ private $managerRegistry; public function __construct(AbstractManagerRegistry $managerRegistry) { $this->managerRegistry = $managerRegistry; } public function getDefaultConnection(): Connection { return $this->managerRegistry->getConnection(); } public function getConnection(string $name): Connection { return $this->managerRegistry->getConnection($name); } } doctrine-bundle/Dbal/RegexSchemaAssetFilter.php 0000644 00000001160 15120025742 0015532 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Dbal; use Doctrine\DBAL\Schema\AbstractAsset; use function preg_match; class RegexSchemaAssetFilter { /** @var string */ private $filterExpression; public function __construct(string $filterExpression) { $this->filterExpression = $filterExpression; } /** @param string|AbstractAsset $assetName */ public function __invoke($assetName): bool { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return (bool) preg_match($this->filterExpression, $assetName); } } doctrine-bundle/Dbal/SchemaAssetsFilterManager.php 0000644 00000001374 15120025742 0016224 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Dbal; use Doctrine\DBAL\Schema\AbstractAsset; /** * Manages schema filters passed to Connection::setSchemaAssetsFilter() */ class SchemaAssetsFilterManager { /** @var callable[] */ private $schemaAssetFilters; /** @param callable[] $schemaAssetFilters */ public function __construct(array $schemaAssetFilters) { $this->schemaAssetFilters = $schemaAssetFilters; } /** @param string|AbstractAsset $assetName */ public function __invoke($assetName): bool { foreach ($this->schemaAssetFilters as $schemaAssetFilter) { if ($schemaAssetFilter($assetName) === false) { return false; } } return true; } } doctrine-bundle/DependencyInjection/Compiler/CacheCompatibilityPass.php 0000644 00000012255 15120025742 0022415 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\Common\Cache\CacheProvider; use Doctrine\Common\Cache\Psr6\CacheAdapter; use Doctrine\Common\Cache\Psr6\DoctrineProvider; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use function array_keys; use function assert; use function in_array; use function is_a; use function trigger_deprecation; /** @internal */ final class CacheCompatibilityPass implements CompilerPassInterface { private const CONFIGURATION_TAG = 'doctrine.orm.configuration'; private const CACHE_METHODS_PSR6_SUPPORT = [ 'setMetadataCache', 'setQueryCache', 'setResultCache', ]; public function process(ContainerBuilder $container): void { foreach (array_keys($container->findTaggedServiceIds(self::CONFIGURATION_TAG)) as $id) { foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) { if ($methodCall[0] === 'setSecondLevelCacheConfiguration') { $this->updateSecondLevelCache($container, $methodCall[1][0]); continue; } if (! in_array($methodCall[0], self::CACHE_METHODS_PSR6_SUPPORT, true)) { continue; } $aliasId = (string) $methodCall[1][0]; $definitionId = (string) $container->getAlias($aliasId); $this->wrapIfNecessary($container, $aliasId, $definitionId, true); } } } private function updateSecondLevelCache(ContainerBuilder $container, Definition $slcConfigDefinition): void { foreach ($slcConfigDefinition->getMethodCalls() as $methodCall) { if ($methodCall[0] !== 'setCacheFactory') { continue; } $factoryDefinition = $methodCall[1][0]; assert($factoryDefinition instanceof Definition); $aliasId = (string) $factoryDefinition->getArgument(1); $this->wrapIfNecessary($container, $aliasId, (string) $container->getAlias($aliasId), false); foreach ($factoryDefinition->getMethodCalls() as $factoryMethodCall) { if ($factoryMethodCall[0] !== 'setRegion') { continue; } $regionDefinition = $container->getDefinition($factoryMethodCall[1][0]); // Get inner service for FileLock if ($regionDefinition->getClass() === '%doctrine.orm.second_level_cache.filelock_region.class%') { $regionDefinition = $container->getDefinition($regionDefinition->getArgument(0)); } // We don't know how to adjust custom region classes if ($regionDefinition->getClass() !== '%doctrine.orm.second_level_cache.default_region.class%') { continue; } $driverId = (string) $regionDefinition->getArgument(1); if (! $container->hasAlias($driverId)) { continue; } $this->wrapIfNecessary($container, $driverId, (string) $container->getAlias($driverId), false); } break; } } private function createCompatibilityLayerDefinition(ContainerBuilder $container, string $definitionId, bool $shouldBePsr6): ?Definition { $definition = $container->getDefinition($definitionId); while (! $definition->getClass() && $definition instanceof ChildDefinition) { $definition = $container->findDefinition($definition->getParent()); } if ($shouldBePsr6 === is_a($definition->getClass(), CacheItemPoolInterface::class, true)) { return null; } $targetClass = CacheProvider::class; $targetFactory = DoctrineProvider::class; if ($shouldBePsr6) { $targetClass = CacheItemPoolInterface::class; $targetFactory = CacheAdapter::class; trigger_deprecation( 'doctrine/doctrine-bundle', '2.4', 'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.', $definitionId ); } return (new Definition($targetClass)) ->setFactory([$targetFactory, 'wrap']) ->addArgument(new Reference($definitionId)); } private function wrapIfNecessary(ContainerBuilder $container, string $aliasId, string $definitionId, bool $shouldBePsr6): void { $compatibilityLayer = $this->createCompatibilityLayerDefinition($container, $definitionId, $shouldBePsr6); if ($compatibilityLayer === null) { return; } $compatibilityLayerId = $definitionId . '.compatibility_layer'; $container->setAlias($aliasId, $compatibilityLayerId); $container->setDefinition($compatibilityLayerId, $compatibilityLayer); } } doctrine-bundle/DependencyInjection/Compiler/CacheSchemaSubscriberPass.php 0000644 00000003510 15120025742 0023022 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use Symfony\Component\Cache\Adapter\PdoAdapter; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; /** * Injects Doctrine DBAL and legacy PDO adapters into their schema subscribers. * * Must be run later after ResolveChildDefinitionsPass. */ class CacheSchemaSubscriberPass implements CompilerPassInterface { /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { // available in Symfony 5.4 and higher /** @psalm-suppress UndefinedClass */ $this->injectAdapters($container, 'doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber', DoctrineDbalAdapter::class); // available in Symfony 5.1 and up to Symfony 5.4 (deprecated) $this->injectAdapters($container, 'doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber', PdoAdapter::class); } private function injectAdapters(ContainerBuilder $container, string $subscriberId, string $class) { if (! $container->hasDefinition($subscriberId)) { return; } $subscriber = $container->getDefinition($subscriberId); $cacheAdaptersReferences = []; foreach ($container->getDefinitions() as $id => $definition) { if ($definition->isAbstract() || $definition->isSynthetic()) { continue; } if ($definition->getClass() !== $class) { continue; } $cacheAdaptersReferences[] = new Reference($id); } $subscriber->replaceArgument(0, $cacheAdaptersReferences); } } doctrine-bundle/DependencyInjection/Compiler/DbalSchemaFilterPass.php 0000644 00000003440 15120025742 0022005 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use function sprintf; /** * Processes the doctrine.dbal.schema_filter */ class DbalSchemaFilterPass implements CompilerPassInterface { /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { $filters = $container->findTaggedServiceIds('doctrine.dbal.schema_filter'); $connectionFilters = []; foreach ($filters as $id => $tagAttributes) { foreach ($tagAttributes as $attributes) { $name = $attributes['connection'] ?? $container->getParameter('doctrine.default_connection'); if (! isset($connectionFilters[$name])) { $connectionFilters[$name] = []; } $connectionFilters[$name][] = new Reference($id); } } foreach ($connectionFilters as $name => $references) { $configurationId = sprintf('doctrine.dbal.%s_connection.configuration', $name); if (! $container->hasDefinition($configurationId)) { continue; } $definition = new ChildDefinition('doctrine.dbal.schema_asset_filter_manager'); $definition->setArgument(0, $references); $id = sprintf('doctrine.dbal.%s_schema_asset_filter_manager', $name); $container->setDefinition($id, $definition); $container->findDefinition($configurationId) ->addMethodCall('setSchemaAssetsFilter', [new Reference($id)]); } } } doctrine-bundle/DependencyInjection/Compiler/DoctrineOrmMappingsPass.php 0000644 00000023707 15120025742 0022610 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; use Doctrine\ORM\Mapping\Driver\YamlDriver; use Doctrine\Persistence\Mapping\Driver\PHPDriver; use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterMappingsPass; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** * Class for Symfony bundles to configure mappings for model classes not in the * auto-mapped folder. * * NOTE: alias is only supported by Symfony 2.6+ and will be ignored with older versions. */ class DoctrineOrmMappingsPass extends RegisterMappingsPass { /** * You should not directly instantiate this class but use one of the * factory methods. * * @param Definition|Reference $driver Driver DI definition or reference. * @param string[] $namespaces List of namespaces handled by $driver. * @param string[] $managerParameters Ordered list of container parameters that * could hold the manager name. * doctrine.default_entity_manager is appended * automatically. * @param string|false $enabledParameter If specified, the compiler pass only executes * if this parameter is defined in the service * container. * @param string[] $aliasMap Map of alias to namespace. */ public function __construct($driver, array $namespaces, array $managerParameters, $enabledParameter = false, array $aliasMap = []) { $managerParameters[] = 'doctrine.default_entity_manager'; parent::__construct( $driver, $namespaces, $managerParameters, 'doctrine.orm.%s_metadata_driver', $enabledParameter, 'doctrine.orm.%s_configuration', 'addEntityNamespace', $aliasMap ); } /** * @param string[] $namespaces Hashmap of directory path to namespace. * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createXmlMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.orm.xml']); $driver = new Definition(XmlDriver::class, [$locator]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } /** * @param string[] $namespaces Hashmap of directory path to namespace * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createYamlMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.orm.yml']); $driver = new Definition(YamlDriver::class, [$locator]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } /** * @param string[] $namespaces Hashmap of directory path to namespace * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createPhpMappingDriver(array $namespaces, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.php']); $driver = new Definition(PHPDriver::class, [$locator]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } /** * @param string[] $namespaces List of namespaces that are handled with annotation mapping * @param string[] $directories List of directories to look for annotated classes * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createAnnotationMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $reader = new Reference('annotation_reader'); $driver = new Definition(AnnotationDriver::class, [$reader, $directories]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } /** * @param string[] $namespaces List of namespaces that are handled with annotation mapping * @param string[] $directories List of directories to look for annotated classes * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createAttributeMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $driver = new Definition(AttributeDriver::class, [$directories]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } /** * @param string[] $namespaces List of namespaces that are handled with static php mapping * @param string[] $directories List of directories to look for static php mapping files * @param string[] $managerParameters List of parameters that could which object manager name * your bundle uses. This compiler pass will automatically * append the parameter name for the default entity manager * to this list. * @param string|false $enabledParameter Service container parameter that must be present to * enable the mapping. Set to false to not do any check, * optional. * @param string[] $aliasMap Map of alias to namespace. * * @return self */ public static function createStaticPhpMappingDriver(array $namespaces, array $directories, array $managerParameters = [], $enabledParameter = false, array $aliasMap = []) { $driver = new Definition(StaticPHPDriver::class, [$directories]); return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter, $aliasMap); } } doctrine-bundle/DependencyInjection/Compiler/EntityListenerPass.php 0000644 00000014051 15120025742 0021636 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver; use Doctrine\Bundle\DoctrineBundle\Mapping\EntityListenerServiceResolver; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use function is_a; use function method_exists; use function sprintf; use function substr; /** * Class for Symfony bundles to register entity listeners */ class EntityListenerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { $resolvers = $this->findAndSortTaggedServices('doctrine.orm.entity_listener', $container); $lazyServiceReferencesByResolver = []; foreach ($resolvers as $reference) { $id = $reference->__toString(); foreach ($container->getDefinition($id)->getTag('doctrine.orm.entity_listener') as $attributes) { $name = $attributes['entity_manager'] ?? $container->getParameter('doctrine.default_entity_manager'); $entityManager = sprintf('doctrine.orm.%s_entity_manager', $name); if (! $container->hasDefinition($entityManager)) { continue; } $resolverId = sprintf('doctrine.orm.%s_entity_listener_resolver', $name); if (! $container->has($resolverId)) { continue; } $resolver = $container->findDefinition($resolverId); $resolver->setPublic(true); if (isset($attributes['entity']) && isset($attributes['event'])) { $this->attachToListener($container, $name, $this->getConcreteDefinitionClass($container->findDefinition($id), $container, $id), $attributes); } $resolverClass = $this->getResolverClass($resolver, $container, $resolverId); $resolverSupportsLazyListeners = is_a($resolverClass, EntityListenerServiceResolver::class, true); $lazyByAttribute = isset($attributes['lazy']) && $attributes['lazy']; if ($lazyByAttribute && ! $resolverSupportsLazyListeners) { throw new InvalidArgumentException(sprintf( 'Lazy-loaded entity listeners can only be resolved by a resolver implementing %s.', EntityListenerServiceResolver::class )); } if (! isset($attributes['lazy']) && $resolverSupportsLazyListeners || $lazyByAttribute) { $listener = $container->findDefinition($id); $resolver->addMethodCall('registerService', [$this->getConcreteDefinitionClass($listener, $container, $id), $id]); // if the resolver uses the default class we will use a service locator for all listeners if ($resolverClass === ContainerEntityListenerResolver::class) { if (! isset($lazyServiceReferencesByResolver[$resolverId])) { $lazyServiceReferencesByResolver[$resolverId] = []; } $lazyServiceReferencesByResolver[$resolverId][$id] = new Reference($id); } else { $listener->setPublic(true); } } else { $resolver->addMethodCall('register', [new Reference($id)]); } } } foreach ($lazyServiceReferencesByResolver as $resolverId => $listenerReferences) { $container->findDefinition($resolverId)->setArgument(0, ServiceLocatorTagPass::register($container, $listenerReferences)); } } /** @param array{entity: class-string, event: string} $attributes */ private function attachToListener(ContainerBuilder $container, string $name, string $class, array $attributes): void { $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $name); if (! $container->has($listenerId)) { return; } $args = [ $attributes['entity'], $class, $attributes['event'], ]; if (isset($attributes['method'])) { $args[] = $attributes['method']; } elseif (! method_exists($class, $attributes['event']) && method_exists($class, '__invoke')) { $args[] = '__invoke'; } $container->findDefinition($listenerId)->addMethodCall('addEntityListener', $args); } private function getResolverClass(Definition $resolver, ContainerBuilder $container, string $id): string { $resolverClass = $this->getConcreteDefinitionClass($resolver, $container, $id); if (substr($resolverClass, 0, 1) === '%') { // resolve container parameter first $resolverClass = $container->getParameterBag()->resolveValue($resolverClass); } return $resolverClass; } private function getConcreteDefinitionClass(Definition $definition, ContainerBuilder $container, string $id): string { $class = $definition->getClass(); if ($class) { return $class; } while ($definition instanceof ChildDefinition) { $definition = $container->findDefinition($definition->getParent()); $class = $definition->getClass(); if ($class) { return $class; } } throw new InvalidArgumentException(sprintf('The service "%s" must define its class.', $id)); } } doctrine-bundle/DependencyInjection/Compiler/IdGeneratorPass.php 0000644 00000006105 15120025742 0021060 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\Bundle\DoctrineBundle\Mapping\ClassMetadataFactory; use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver; use Doctrine\ORM\Mapping\ClassMetadataFactory as ORMClassMetadataFactory; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use function array_combine; use function array_keys; use function array_map; use function sprintf; final class IdGeneratorPass implements CompilerPassInterface { public const ID_GENERATOR_TAG = 'doctrine.id_generator'; public const CONFIGURATION_TAG = 'doctrine.orm.configuration'; public function process(ContainerBuilder $container): void { $generatorIds = array_keys($container->findTaggedServiceIds(self::ID_GENERATOR_TAG)); // when ORM is not enabled if (! $container->hasDefinition('doctrine.orm.configuration') || ! $generatorIds) { return; } $generatorRefs = array_map(static function ($id) { return new Reference($id); }, $generatorIds); $ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs)); $container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false)); foreach ($container->findTaggedServiceIds(self::CONFIGURATION_TAG) as $id => $tags) { $configurationDef = $container->getDefinition($id); $methodCalls = $configurationDef->getMethodCalls(); $metadataDriverImpl = null; foreach ($methodCalls as $i => [$method, $arguments]) { if ($method === 'setMetadataDriverImpl') { $metadataDriverImpl = (string) $arguments[0]; } if ($method !== 'setClassMetadataFactoryName') { continue; } if ($arguments[0] !== ORMClassMetadataFactory::class && $arguments[0] !== ClassMetadataFactory::class) { $class = $container->getReflectionClass($arguments[0]); if ($class && $class->isSubclassOf(ClassMetadataFactory::class)) { break; } continue 2; } $methodCalls[$i] = ['setClassMetadataFactoryName', [ClassMetadataFactory::class]]; } if ($metadataDriverImpl === null) { continue; } $configurationDef->setMethodCalls($methodCalls); $container->register('.' . $metadataDriverImpl, MappingDriver::class) ->setDecoratedService($metadataDriverImpl) ->setArguments([ new Reference(sprintf('.%s.inner', $metadataDriverImpl)), new Reference('doctrine.id_generator_locator'), ]); } } } doctrine-bundle/DependencyInjection/Compiler/MiddlewaresPass.php 0000644 00000004451 15120025742 0021117 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\Bundle\DoctrineBundle\Middleware\ConnectionNameAwareInterface; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use function array_keys; use function in_array; use function is_subclass_of; use function sprintf; final class MiddlewaresPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { if (! $container->hasParameter('doctrine.connections')) { return; } $middlewareAbstractDefs = []; $middlewareConnections = []; foreach ($container->findTaggedServiceIds('doctrine.middleware') as $id => $tags) { $middlewareAbstractDefs[$id] = $container->getDefinition($id); // When a def has doctrine.middleware tags with connection attributes equal to connection names // registration of this middleware is limited to the connections with these names foreach ($tags as $tag) { if (! isset($tag['connection'])) { continue; } $middlewareConnections[$id][] = $tag['connection']; } } foreach (array_keys($container->getParameter('doctrine.connections')) as $name) { $middlewareDefs = []; foreach ($middlewareAbstractDefs as $id => $abstractDef) { if (isset($middlewareConnections[$id]) && ! in_array($name, $middlewareConnections[$id], true)) { continue; } $middlewareDefs[] = $childDef = $container->setDefinition( sprintf('%s.%s', $id, $name), new ChildDefinition($id) ); if (! is_subclass_of($abstractDef->getClass(), ConnectionNameAwareInterface::class)) { continue; } $childDef->addMethodCall('setConnectionName', [$name]); } $container ->getDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name)) ->addMethodCall('setMiddlewares', [$middlewareDefs]); } } } doctrine-bundle/DependencyInjection/Compiler/RemoveLoggingMiddlewarePass.php 0000644 00000001013 15120025742 0023410 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; /** @internal */ final class RemoveLoggingMiddlewarePass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if ($container->has('logger')) { return; } $container->removeDefinition('doctrine.dbal.logging_middleware'); } } doctrine-bundle/DependencyInjection/Compiler/RemoveProfilerControllerPass.php 0000644 00000001142 15120025742 0023655 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; /** @internal */ final class RemoveProfilerControllerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if ($container->has('twig') && $container->has('profiler')) { return; } $container->removeDefinition(ProfilerController::class); } } doctrine-bundle/DependencyInjection/Compiler/ServiceRepositoryCompilerPass.php 0000644 00000002404 15120025742 0024046 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use function array_combine; use function array_keys; use function array_map; final class ServiceRepositoryCompilerPass implements CompilerPassInterface { public const REPOSITORY_SERVICE_TAG = 'doctrine.repository_service'; public function process(ContainerBuilder $container): void { // when ORM is not enabled if (! $container->hasDefinition('doctrine.orm.container_repository_factory')) { return; } $locatorDef = $container->getDefinition('doctrine.orm.container_repository_factory'); $repoServiceIds = array_keys($container->findTaggedServiceIds(self::REPOSITORY_SERVICE_TAG)); $repoReferences = array_map(static function ($id) { return new Reference($id); }, $repoServiceIds); $ref = ServiceLocatorTagPass::register($container, array_combine($repoServiceIds, $repoReferences)); $locatorDef->replaceArgument(0, $ref); } } doctrine-bundle/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php 0000644 00000004632 15120025742 0023067 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use Symfony\Component\Cache\Adapter\PdoAdapter; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; use Symfony\Component\Lock\Store\DoctrineDbalStore; use Symfony\Component\Lock\Store\PdoStore; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection; use Symfony\Component\Messenger\Transport\Doctrine\Connection as LegacyConnection; use function array_keys; /** * Blacklist tables used by well-known Symfony classes. * * @deprecated Implement your own include/exclude mechanism */ class WellKnownSchemaFilterPass implements CompilerPassInterface { /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { $blacklist = []; foreach ($container->getDefinitions() as $definition) { if ($definition->isAbstract() || $definition->isSynthetic()) { continue; } switch ($definition->getClass()) { case DoctrineDbalAdapter::class: case PdoAdapter::class: $blacklist[] = $definition->getArguments()[3]['db_table'] ?? 'cache_items'; break; case PdoSessionHandler::class: $blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'sessions'; break; case DoctrineDbalStore::class: case PdoStore::class: $blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'lock_keys'; break; case LegacyConnection::class: case Connection::class: $blacklist[] = $definition->getArguments()[0]['table_name'] ?? 'messenger_messages'; break; } } if (! $blacklist) { return; } $definition = $container->getDefinition('doctrine.dbal.well_known_schema_asset_filter'); $definition->replaceArgument(0, $blacklist); foreach (array_keys($container->getParameter('doctrine.connections')) as $name) { $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); } } } doctrine-bundle/DependencyInjection/Configuration.php 0000644 00000110256 15120025742 0017066 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection; use Doctrine\Common\Proxy\AbstractProxyFactory; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadataFactory; use ReflectionClass; use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\DependencyInjection\Exception\LogicException; use function array_intersect_key; use function array_key_exists; use function array_keys; use function array_pop; use function assert; use function class_exists; use function constant; use function count; use function defined; use function implode; use function in_array; use function is_array; use function is_bool; use function is_int; use function is_string; use function key; use function method_exists; use function reset; use function sprintf; use function strlen; use function strpos; use function strtoupper; use function substr; use function trigger_deprecation; /** * This class contains the configuration information for the bundle * * This information is solely responsible for how the different configuration * sections are normalized, and merged. */ class Configuration implements ConfigurationInterface { /** @var bool */ private $debug; /** @param bool $debug Whether to use the debug mode */ public function __construct(bool $debug) { $this->debug = $debug; } public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('doctrine'); $rootNode = $treeBuilder->getRootNode(); $this->addDbalSection($rootNode); $this->addOrmSection($rootNode); return $treeBuilder; } /** * Add DBAL section to configuration tree */ private function addDbalSection(ArrayNodeDefinition $node): void { $node ->children() ->arrayNode('dbal') ->beforeNormalization() ->ifTrue(static function ($v) { return is_array($v) && ! array_key_exists('connections', $v) && ! array_key_exists('connection', $v); }) ->then(static function ($v) { // Key that should not be rewritten to the connection config $excludedKeys = ['default_connection' => true, 'types' => true, 'type' => true]; $connection = []; foreach ($v as $key => $value) { if (isset($excludedKeys[$key])) { continue; } $connection[$key] = $v[$key]; unset($v[$key]); } $v['default_connection'] = isset($v['default_connection']) ? (string) $v['default_connection'] : 'default'; $v['connections'] = [$v['default_connection'] => $connection]; return $v; }) ->end() ->children() ->scalarNode('default_connection')->end() ->end() ->fixXmlConfig('type') ->children() ->arrayNode('types') ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() ->ifString() ->then(static function ($v) { return ['class' => $v]; }) ->end() ->children() ->scalarNode('class')->isRequired()->end() ->booleanNode('commented') ->setDeprecated( ...$this->getDeprecationMsg('The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0.', '2.0') ) ->end() ->end() ->end() ->end() ->end() ->fixXmlConfig('connection') ->append($this->getDbalConnectionsNode()) ->end(); } /** * Return the dbal connections node */ private function getDbalConnectionsNode(): ArrayNodeDefinition { $treeBuilder = new TreeBuilder('connections'); $node = $treeBuilder->getRootNode(); $connectionNode = $node ->requiresAtLeastOneElement() ->useAttributeAsKey('name') ->prototype('array'); assert($connectionNode instanceof ArrayNodeDefinition); $this->configureDbalDriverNode($connectionNode); $collationKey = defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY') ? 'collate' : 'collation'; $connectionNode ->fixXmlConfig('option') ->fixXmlConfig('mapping_type') ->fixXmlConfig('slave') ->fixXmlConfig('replica') ->fixXmlConfig('shard') ->fixXmlConfig('default_table_option') ->children() ->scalarNode('driver')->defaultValue('pdo_mysql')->end() ->scalarNode('platform_service')->end() ->booleanNode('auto_commit')->end() ->scalarNode('schema_filter')->end() ->booleanNode('logging')->defaultValue($this->debug)->end() ->booleanNode('profiling')->defaultValue($this->debug)->end() ->booleanNode('profiling_collect_backtrace') ->defaultValue(false) ->info('Enables collecting backtraces when profiling is enabled') ->end() ->booleanNode('profiling_collect_schema_errors') ->defaultValue(true) ->info('Enables collecting schema errors when profiling is enabled') ->end() ->scalarNode('server_version')->end() ->scalarNode('driver_class')->end() ->scalarNode('wrapper_class')->end() ->scalarNode('shard_manager_class') ->setDeprecated( ...$this->getDeprecationMsg('The "shard_manager_class" configuration is deprecated and not supported anymore using DBAL 3.', '2.7') ) ->end() ->scalarNode('shard_choser') ->setDeprecated( ...$this->getDeprecationMsg('The "shard_choser" configuration is deprecated and not supported anymore using DBAL 3.', '2.7') ) ->end() ->scalarNode('shard_choser_service') ->setDeprecated( ...$this->getDeprecationMsg('The "shard_choser_service" configuration is deprecated and not supported anymore using DBAL 3.', '2.7') ) ->end() ->booleanNode('keep_slave') ->setDeprecated( ...$this->getDeprecationMsg('The "keep_slave" configuration key is deprecated since doctrine-bundle 2.2. Use the "keep_replica" configuration key instead.', '2.2') ) ->end() ->booleanNode('keep_replica')->end() ->arrayNode('options') ->useAttributeAsKey('key') ->prototype('variable')->end() ->end() ->arrayNode('mapping_types') ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->arrayNode('default_table_options') ->info(sprintf( "This option is used by the schema-tool and affects generated SQL. Possible keys include 'charset','%s', and 'engine'.", $collationKey )) ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->end(); // dbal < 2.11 $slaveNode = $connectionNode ->children() ->arrayNode('slaves') ->setDeprecated( ...$this->getDeprecationMsg('The "slaves" configuration key will be renamed to "replicas" in doctrine-bundle 3.0. "slaves" is deprecated since doctrine-bundle 2.2.', '2.2') ) ->useAttributeAsKey('name') ->prototype('array'); $this->configureDbalDriverNode($slaveNode); // dbal >= 2.11 $replicaNode = $connectionNode ->children() ->arrayNode('replicas') ->useAttributeAsKey('name') ->prototype('array'); $this->configureDbalDriverNode($replicaNode); $shardNode = $connectionNode ->children() ->arrayNode('shards') ->setDeprecated( ...$this->getDeprecationMsg('The "shards" configuration is deprecated and not supported anymore using DBAL 3.', '2.7') ) ->prototype('array'); $shardNode ->children() ->integerNode('id') ->min(1) ->isRequired() ->end() ->end(); $this->configureDbalDriverNode($shardNode); return $node; } /** * Adds config keys related to params processed by the DBAL drivers * * These keys are available for replica configurations too. */ private function configureDbalDriverNode(ArrayNodeDefinition $node): void { $node ->validate() ->always(static function (array $values) { if (! isset($values['url'])) { return $values; } $urlConflictingOptions = ['host' => true, 'port' => true, 'user' => true, 'password' => true, 'path' => true, 'dbname' => true, 'unix_socket' => true, 'memory' => true]; $urlConflictingValues = array_keys(array_intersect_key($values, $urlConflictingOptions)); if ($urlConflictingValues) { $tail = count($urlConflictingValues) > 1 ? sprintf('or "%s" options', array_pop($urlConflictingValues)) : 'option'; trigger_deprecation( 'doctrine/doctrine-bundle', '2.4', 'Setting the "doctrine.dbal.%s" %s while the "url" one is defined is deprecated', implode('", "', $urlConflictingValues), $tail ); } return $values; }) ->end() ->children() ->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end() ->scalarNode('dbname')->end() ->scalarNode('host')->info('Defaults to "localhost" at runtime.')->end() ->scalarNode('port')->info('Defaults to null at runtime.')->end() ->scalarNode('user')->info('Defaults to "root" at runtime.')->end() ->scalarNode('password')->info('Defaults to null at runtime.')->end() ->booleanNode('override_url')->setDeprecated(...$this->getDeprecationMsg('The "doctrine.dbal.override_url" configuration key is deprecated.', '2.4'))->end() ->scalarNode('dbname_suffix')->end() ->scalarNode('application_name')->end() ->scalarNode('charset')->end() ->scalarNode('path')->end() ->booleanNode('memory')->end() ->scalarNode('unix_socket')->info('The unix socket to use for MySQL')->end() ->booleanNode('persistent')->info('True to use as persistent connection for the ibm_db2 driver')->end() ->scalarNode('protocol')->info('The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)')->end() ->booleanNode('service') ->info('True to use SERVICE_NAME as connection parameter instead of SID for Oracle') ->end() ->scalarNode('servicename') ->info( 'Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter ' . 'for Oracle depending on the service parameter.' ) ->end() ->scalarNode('sessionMode') ->info('The session mode to use for the oci8 driver') ->end() ->scalarNode('server') ->info('The name of a running database server to connect to for SQL Anywhere.') ->end() ->scalarNode('default_dbname') ->info( 'Override the default database (postgres) to connect to for PostgreSQL connexion.' ) ->end() ->scalarNode('sslmode') ->info( 'Determines whether or with what priority a SSL TCP/IP connection will be negotiated with ' . 'the server for PostgreSQL.' ) ->end() ->scalarNode('sslrootcert') ->info( 'The name of a file containing SSL certificate authority (CA) certificate(s). ' . 'If the file exists, the server\'s certificate will be verified to be signed by one of these authorities.' ) ->end() ->scalarNode('sslcert') ->info( 'The path to the SSL client certificate file for PostgreSQL.' ) ->end() ->scalarNode('sslkey') ->info( 'The path to the SSL client key file for PostgreSQL.' ) ->end() ->scalarNode('sslcrl') ->info( 'The file name of the SSL certificate revocation list for PostgreSQL.' ) ->end() ->booleanNode('pooled')->info('True to use a pooled server with the oci8/pdo_oracle driver')->end() ->booleanNode('MultipleActiveResultSets')->info('Configuring MultipleActiveResultSets for the pdo_sqlsrv driver')->end() ->booleanNode('use_savepoints')->info('Use savepoints for nested transactions')->end() ->scalarNode('instancename') ->info( 'Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection.' . ' It is generally used to connect to an Oracle RAC server to select the name' . ' of a particular instance.' ) ->end() ->scalarNode('connectstring') ->info( 'Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.' . 'When using this option, you will still need to provide the user and password parameters, but the other ' . 'parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods' . ' from Doctrine\DBAL\Connection will no longer function as expected.' ) ->end() ->end() ->beforeNormalization() ->ifTrue(static function ($v) { return ! isset($v['sessionMode']) && isset($v['session_mode']); }) ->then(static function ($v) { $v['sessionMode'] = $v['session_mode']; unset($v['session_mode']); return $v; }) ->end() ->beforeNormalization() ->ifTrue(static function ($v) { return ! isset($v['MultipleActiveResultSets']) && isset($v['multiple_active_result_sets']); }) ->then(static function ($v) { $v['MultipleActiveResultSets'] = $v['multiple_active_result_sets']; unset($v['multiple_active_result_sets']); return $v; }) ->end(); } /** * Add the ORM section to configuration tree */ private function addOrmSection(ArrayNodeDefinition $node): void { $node ->children() ->arrayNode('orm') ->beforeNormalization() ->ifTrue(static function ($v) { if (! empty($v) && ! class_exists(EntityManager::class)) { throw new LogicException('The doctrine/orm package is required when the doctrine.orm config is set.'); } return $v === null || (is_array($v) && ! array_key_exists('entity_managers', $v) && ! array_key_exists('entity_manager', $v)); }) ->then(static function ($v) { $v = (array) $v; // Key that should not be rewritten to the entity-manager config $excludedKeys = [ 'default_entity_manager' => true, 'auto_generate_proxy_classes' => true, 'proxy_dir' => true, 'proxy_namespace' => true, 'resolve_target_entities' => true, 'resolve_target_entity' => true, 'controller_resolver' => true, ]; $entityManager = []; foreach ($v as $key => $value) { if (isset($excludedKeys[$key])) { continue; } $entityManager[$key] = $v[$key]; unset($v[$key]); } $v['default_entity_manager'] = isset($v['default_entity_manager']) ? (string) $v['default_entity_manager'] : 'default'; $v['entity_managers'] = [$v['default_entity_manager'] => $entityManager]; return $v; }) ->end() ->children() ->scalarNode('default_entity_manager')->end() ->scalarNode('auto_generate_proxy_classes')->defaultValue(false) ->info('Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED"') ->validate() ->ifTrue(function ($v) { $generationModes = $this->getAutoGenerateModes(); if (is_int($v) && in_array($v, $generationModes['values']/*array(0, 1, 2, 3)*/)) { return false; } if (is_bool($v)) { return false; } if (is_string($v)) { if (in_array(strtoupper($v), $generationModes['names']/*array('NEVER', 'ALWAYS', 'FILE_NOT_EXISTS', 'EVAL', 'FILE_NOT_EXISTS_OR_CHANGED')*/)) { return false; } } return true; }) ->thenInvalid('Invalid auto generate mode value %s') ->end() ->validate() ->ifString() ->then(static function ($v) { return constant('Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_' . strtoupper($v)); }) ->end() ->end() ->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end() ->scalarNode('proxy_namespace')->defaultValue('Proxies')->end() ->arrayNode('controller_resolver') ->canBeDisabled() ->children() ->booleanNode('auto_mapping') ->defaultTrue() ->info('Set to false to disable using route placeholders as lookup criteria when the primary key doesn\'t match the argument name') ->end() ->booleanNode('evict_cache') ->info('Set to true to fetch the entity from the database instead of using the cache, if any') ->defaultFalse() ->end() ->end() ->end() ->end() ->fixXmlConfig('entity_manager') ->append($this->getOrmEntityManagersNode()) ->fixXmlConfig('resolve_target_entity', 'resolve_target_entities') ->append($this->getOrmTargetEntityResolverNode()) ->end() ->end(); } /** * Return ORM target entity resolver node */ private function getOrmTargetEntityResolverNode(): NodeDefinition { $treeBuilder = new TreeBuilder('resolve_target_entities'); $node = $treeBuilder->getRootNode(); $node ->useAttributeAsKey('interface') ->prototype('scalar') ->cannotBeEmpty() ->end(); return $node; } /** * Return ORM entity listener node */ private function getOrmEntityListenersNode(): NodeDefinition { $treeBuilder = new TreeBuilder('entity_listeners'); $node = $treeBuilder->getRootNode(); $normalizer = static function ($mappings) { $entities = []; foreach ($mappings as $entityClass => $mapping) { $listeners = []; foreach ($mapping as $listenerClass => $listenerEvent) { $events = []; foreach ($listenerEvent as $eventType => $eventMapping) { if ($eventMapping === null) { $eventMapping = [null]; } foreach ($eventMapping as $method) { $events[] = [ 'type' => $eventType, 'method' => $method, ]; } } $listeners[] = [ 'class' => $listenerClass, 'event' => $events, ]; } $entities[] = [ 'class' => $entityClass, 'listener' => $listeners, ]; } return ['entities' => $entities]; }; $node ->beforeNormalization() // Yaml normalization ->ifTrue(static function ($v) { return is_array(reset($v)) && is_string(key(reset($v))); }) ->then($normalizer) ->end() ->fixXmlConfig('entity', 'entities') ->children() ->arrayNode('entities') ->useAttributeAsKey('class') ->prototype('array') ->fixXmlConfig('listener') ->children() ->arrayNode('listeners') ->useAttributeAsKey('class') ->prototype('array') ->fixXmlConfig('event') ->children() ->arrayNode('events') ->prototype('array') ->children() ->scalarNode('type')->end() ->scalarNode('method')->defaultNull()->end() ->end() ->end() ->end() ->end() ->end() ->end() ->end() ->end() ->end() ->end(); return $node; } /** * Return ORM entity manager node */ private function getOrmEntityManagersNode(): ArrayNodeDefinition { $treeBuilder = new TreeBuilder('entity_managers'); $node = $treeBuilder->getRootNode(); $node ->requiresAtLeastOneElement() ->useAttributeAsKey('name') ->prototype('array') ->addDefaultsIfNotSet() ->append($this->getOrmCacheDriverNode('query_cache_driver')) ->append($this->getOrmCacheDriverNode('metadata_cache_driver')) ->append($this->getOrmCacheDriverNode('result_cache_driver')) ->append($this->getOrmEntityListenersNode()) ->fixXmlConfig('schema_ignore_class', 'schema_ignore_classes') ->children() ->scalarNode('connection')->end() ->scalarNode('class_metadata_factory_name')->defaultValue(ClassMetadataFactory::class)->end() ->scalarNode('default_repository_class')->defaultValue(EntityRepository::class)->end() ->scalarNode('auto_mapping')->defaultFalse()->end() ->scalarNode('naming_strategy')->defaultValue('doctrine.orm.naming_strategy.default')->end() ->scalarNode('quote_strategy')->defaultValue('doctrine.orm.quote_strategy.default')->end() ->scalarNode('entity_listener_resolver')->defaultNull()->end() ->scalarNode('repository_factory')->defaultValue('doctrine.orm.container_repository_factory')->end() ->arrayNode('schema_ignore_classes') ->prototype('scalar')->end() ->end() ->end() ->children() ->arrayNode('second_level_cache') ->children() ->append($this->getOrmCacheDriverNode('region_cache_driver')) ->scalarNode('region_lock_lifetime')->defaultValue(60)->end() ->booleanNode('log_enabled')->defaultValue($this->debug)->end() ->scalarNode('region_lifetime')->defaultValue(3600)->end() ->booleanNode('enabled')->defaultValue(true)->end() ->scalarNode('factory')->end() ->end() ->fixXmlConfig('region') ->children() ->arrayNode('regions') ->useAttributeAsKey('name') ->prototype('array') ->children() ->append($this->getOrmCacheDriverNode('cache_driver')) ->scalarNode('lock_path')->defaultValue('%kernel.cache_dir%/doctrine/orm/slc/filelock')->end() ->scalarNode('lock_lifetime')->defaultValue(60)->end() ->scalarNode('type')->defaultValue('default')->end() ->scalarNode('lifetime')->defaultValue(0)->end() ->scalarNode('service')->end() ->scalarNode('name')->end() ->end() ->end() ->end() ->end() ->fixXmlConfig('logger') ->children() ->arrayNode('loggers') ->useAttributeAsKey('name') ->prototype('array') ->children() ->scalarNode('name')->end() ->scalarNode('service')->end() ->end() ->end() ->end() ->end() ->end() ->end() ->fixXmlConfig('hydrator') ->children() ->arrayNode('hydrators') ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->end() ->fixXmlConfig('mapping') ->children() ->arrayNode('mappings') ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() ->ifString() ->then(static function ($v) { return ['type' => $v]; }) ->end() ->treatNullLike([]) ->treatFalseLike(['mapping' => false]) ->performNoDeepMerging() ->children() ->scalarNode('mapping')->defaultValue(true)->end() ->scalarNode('type')->end() ->scalarNode('dir')->end() ->scalarNode('alias')->end() ->scalarNode('prefix')->end() ->booleanNode('is_bundle')->end() ->end() ->end() ->end() ->arrayNode('dql') ->fixXmlConfig('string_function') ->fixXmlConfig('numeric_function') ->fixXmlConfig('datetime_function') ->children() ->arrayNode('string_functions') ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->arrayNode('numeric_functions') ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->arrayNode('datetime_functions') ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() ->end() ->end() ->end() ->fixXmlConfig('filter') ->children() ->arrayNode('filters') ->info('Register SQL Filters in the entity manager') ->useAttributeAsKey('name') ->prototype('array') ->beforeNormalization() ->ifString() ->then(static function ($v) { return ['class' => $v]; }) ->end() ->beforeNormalization() // The content of the XML node is returned as the "value" key so we need to rename it ->ifTrue(static function ($v) { return is_array($v) && isset($v['value']); }) ->then(static function ($v) { $v['class'] = $v['value']; unset($v['value']); return $v; }) ->end() ->fixXmlConfig('parameter') ->children() ->scalarNode('class')->isRequired()->end() ->booleanNode('enabled')->defaultFalse()->end() ->arrayNode('parameters') ->useAttributeAsKey('name') ->prototype('variable')->end() ->end() ->end() ->end() ->end() ->end() ->end(); return $node; } /** * Return a ORM cache driver node for an given entity manager */ private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition { $treeBuilder = new TreeBuilder($name); $node = $treeBuilder->getRootNode(); $node ->beforeNormalization() ->ifString() ->then(static function ($v): array { return ['type' => $v]; }) ->end() ->children() ->scalarNode('type')->defaultNull()->end() ->scalarNode('id')->end() ->scalarNode('pool')->end() ->end(); if ($name !== 'metadata_cache_driver') { $node->addDefaultsIfNotSet(); } return $node; } /** * Find proxy auto generate modes for their names and int values * * @return array{names: list<string>, values: list<int>} */ private function getAutoGenerateModes(): array { $constPrefix = 'AUTOGENERATE_'; $prefixLen = strlen($constPrefix); $refClass = new ReflectionClass(AbstractProxyFactory::class); $constsArray = $refClass->getConstants(); $namesArray = []; $valuesArray = []; foreach ($constsArray as $key => $value) { if (strpos($key, $constPrefix) !== 0) { continue; } $namesArray[] = substr($key, $prefixLen); $valuesArray[] = (int) $value; } return [ 'names' => $namesArray, 'values' => $valuesArray, ]; } /** * Returns the correct deprecation param's as an array for setDeprecated. * * Symfony/Config v5.1 introduces a deprecation notice when calling * setDeprecation() with less than 3 args and the getDeprecation method was * introduced at the same time. By checking if getDeprecation() exists, * we can determine the correct param count to use when calling setDeprecated. * * @return list<string>|array{0:string, 1: numeric-string, string} */ private function getDeprecationMsg(string $message, string $version): array { if (method_exists(BaseNode::class, 'getDeprecation')) { return [ 'doctrine/doctrine-bundle', $version, $message, ]; } return [$message]; } } doctrine-bundle/DependencyInjection/DoctrineExtension.php 0000644 00000150154 15120025743 0017725 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection; use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener; use Doctrine\Bundle\DoctrineBundle\Attribute\AsMiddleware; use Doctrine\Bundle\DoctrineBundle\CacheWarmer\DoctrineMetadataCacheWarmer; use Doctrine\Bundle\DoctrineBundle\Command\Proxy\ImportDoctrineCommand; use Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider; use Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass; use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface; use Doctrine\DBAL\Logging\LoggerChain; use Doctrine\DBAL\Sharding\PoolingShardConnection; use Doctrine\DBAL\Sharding\PoolingShardManager; use Doctrine\DBAL\Tools\Console\Command\ImportCommand; use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Id\AbstractIdGenerator; use Doctrine\ORM\Proxy\Autoloader; use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand; use Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand; use Doctrine\ORM\Tools\Export\ClassMetadataExporter; use Doctrine\ORM\UnitOfWork; use LogicException; use ReflectionMethod; use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension; use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber; use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; use Symfony\Bridge\Doctrine\Middleware\Debug\Middleware as SfDebugMiddleware; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaSubscriber; use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber; use Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber; use Symfony\Bridge\Doctrine\SchemaListener\RememberMeTokenProviderDoctrineSchemaSubscriber; use Symfony\Bridge\Doctrine\Validator\DoctrineLoader; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Form\AbstractType; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; use function array_intersect_key; use function array_keys; use function class_exists; use function interface_exists; use function is_dir; use function method_exists; use function reset; use function sprintf; use function str_replace; use function trigger_deprecation; use const PHP_VERSION_ID; /** * DoctrineExtension is an extension for the Doctrine DBAL and ORM library. */ class DoctrineExtension extends AbstractDoctrineExtension { /** @var string */ private $defaultConnection; /** * {@inheritDoc} */ public function load(array $configs, ContainerBuilder $container) { $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); if (! empty($config['dbal'])) { $this->dbalLoad($config['dbal'], $container); $this->loadMessengerServices($container); } if (empty($config['orm'])) { return; } if (empty($config['dbal'])) { throw new LogicException('Configuring the ORM layer requires to configure the DBAL layer as well.'); } $this->ormLoad($config['orm'], $container); } /** * Loads the DBAL configuration. * * Usage example: * * <doctrine:dbal id="myconn" dbname="sfweb" user="root" /> * * @param array<string, mixed> $config An array of configuration settings * @param ContainerBuilder $container A ContainerBuilder instance */ protected function dbalLoad(array $config, ContainerBuilder $container) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('dbal.xml'); if (class_exists(ImportCommand::class)) { $container->register('doctrine.database_import_command', ImportDoctrineCommand::class) ->addTag('console.command', ['command' => 'doctrine:database:import']); } if (empty($config['default_connection'])) { $keys = array_keys($config['connections']); $config['default_connection'] = reset($keys); } $this->defaultConnection = $config['default_connection']; $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $this->defaultConnection)); $container->getAlias('database_connection')->setPublic(true); $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $this->defaultConnection), false)); $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']); $connections = []; foreach (array_keys($config['connections']) as $name) { $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name); } $container->setParameter('doctrine.connections', $connections); $container->setParameter('doctrine.default_connection', $this->defaultConnection); $connWithLogging = []; $connWithProfiling = []; $connWithBacktrace = []; foreach ($config['connections'] as $name => $connection) { if ($connection['logging']) { $connWithLogging[] = $name; } if ($connection['profiling']) { $connWithProfiling[] = $name; if ($connection['profiling_collect_backtrace']) { $connWithBacktrace[] = $name; } } $this->loadDbalConnection($name, $connection, $container); } /** @psalm-suppress UndefinedClass */ $container->registerForAutoconfiguration(MiddlewareInterface::class)->addTag('doctrine.middleware'); if (PHP_VERSION_ID >= 80000 && method_exists(ContainerBuilder::class, 'registerAttributeForAutoconfiguration')) { $container->registerAttributeForAutoconfiguration(AsMiddleware::class, static function (ChildDefinition $definition, AsMiddleware $attribute) { if ($attribute->connections === []) { $definition->addTag('doctrine.middleware'); return; } foreach ($attribute->connections as $connName) { $definition->addTag('doctrine.middleware', ['connection' => $connName]); } }); } $this->useMiddlewaresIfAvailable($container, $connWithLogging, $connWithProfiling, $connWithBacktrace); } /** * Loads a configured DBAL connection. * * @param string $name The name of the connection * @param array<string, mixed> $connection A dbal connection configuration. * @param ContainerBuilder $container A ContainerBuilder instance */ protected function loadDbalConnection($name, array $connection, ContainerBuilder $container) { $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration')); $logger = null; /** @psalm-suppress UndefinedClass */ if (! interface_exists(MiddlewareInterface::class) && $connection['logging']) { $logger = new Reference('doctrine.dbal.logger'); } unset($connection['logging']); $dataCollectorDefinition = $container->getDefinition('data_collector.doctrine'); $dataCollectorDefinition->replaceArgument(1, $connection['profiling_collect_schema_errors']); if (! $this->isSfDebugMiddlewareAvailable() && $connection['profiling']) { $profilingAbstractId = $connection['profiling_collect_backtrace'] ? 'doctrine.dbal.logger.backtrace' : 'doctrine.dbal.logger.profiling'; $profilingLoggerId = $profilingAbstractId . '.' . $name; $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId)); $profilingLogger = new Reference($profilingLoggerId); $dataCollectorDefinition->addMethodCall('addLogger', [$name, $profilingLogger]); if ($logger !== null) { $chainLogger = $container->register( 'doctrine.dbal.logger.chain', LoggerChain::class ); $chainLogger->addArgument([$logger, $profilingLogger]); $loggerId = 'doctrine.dbal.logger.chain.' . $name; $container->setDefinition($loggerId, $chainLogger); $logger = new Reference($loggerId); } else { $logger = $profilingLogger; } } unset( $connection['profiling'], $connection['profiling_collect_backtrace'], $connection['profiling_collect_schema_errors'] ); if (isset($connection['auto_commit'])) { $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]); } unset($connection['auto_commit']); if (isset($connection['schema_filter']) && $connection['schema_filter']) { $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]); $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition); } unset($connection['schema_filter']); if ($logger) { $configuration->addMethodCall('setSQLLogger', [$logger]); } // event manager $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager')); // connection $options = $this->getConnectionOptions($connection); $connectionId = sprintf('doctrine.dbal.%s_connection', $name); $def = $container ->setDefinition($connectionId, new ChildDefinition('doctrine.dbal.connection')) ->setPublic(true) ->setArguments([ $options, new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)), new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)), $connection['mapping_types'], ]); $container ->registerAliasForArgument($connectionId, Connection::class, sprintf('%sConnection', $name)) ->setPublic(false); // Set class in case "wrapper_class" option was used to assist IDEs if (isset($options['wrapperClass'])) { $def->setClass($options['wrapperClass']); } if (! empty($connection['use_savepoints'])) { $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]); } // Create a shard_manager for this connection if (isset($options['shards'])) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.7', 'Using shards for connection "%s" is deprecated. DBAL 3 does not support shards anymore.', $name ); $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference($connectionId)]); $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition); } // dbal < 2.11 BC layer if (! interface_exists(ConnectionProvider::class)) { return; } $container->setDefinition( ManagerRegistryAwareConnectionProvider::class, new Definition(ManagerRegistryAwareConnectionProvider::class, [$container->getDefinition('doctrine')]) ); } /** * @param array<string, mixed> $connection * * @return mixed[] */ protected function getConnectionOptions(array $connection): array { $options = $connection; $connectionDefaults = [ 'host' => 'localhost', 'port' => null, 'user' => 'root', 'password' => null, ]; if ($options['override_url'] ?? false) { $options['connection_override_options'] = array_intersect_key($options, ['dbname' => null] + $connectionDefaults); } unset($options['override_url']); $options += $connectionDefaults; foreach (['shards', 'replicas', 'slaves'] as $connectionKey) { foreach (array_keys($options[$connectionKey]) as $name) { $options[$connectionKey][$name] += $connectionDefaults; } } if (isset($options['platform_service'])) { $options['platform'] = new Reference($options['platform_service']); unset($options['platform_service']); } unset($options['mapping_types']); if (isset($options['shard_choser_service'])) { $options['shard_choser'] = new Reference($options['shard_choser_service']); unset($options['shard_choser_service']); } foreach ( [ 'options' => 'driverOptions', 'driver_class' => 'driverClass', 'wrapper_class' => 'wrapperClass', 'keep_slave' => 'keepReplica', 'keep_replica' => 'keepReplica', 'replicas' => 'replica', 'shard_choser' => 'shardChoser', 'shard_manager_class' => 'shardManagerClass', 'server_version' => 'serverVersion', 'default_table_options' => 'defaultTableOptions', ] as $old => $new ) { if (! isset($options[$old])) { continue; } $options[$new] = $options[$old]; unset($options[$old]); } if (! empty($options['slaves']) && ! empty($options['replica']) && ! empty($options['shards'])) { throw new InvalidArgumentException('Sharding and primary-replica connection cannot be used together'); } foreach (['shards', 'replica', 'slaves'] as $connectionKey) { foreach ($options[$connectionKey] as $name => $value) { $driverOptions = $value['driverOptions'] ?? []; $parentDriverOptions = $options['driverOptions'] ?? []; if ($driverOptions === [] && $parentDriverOptions === []) { continue; } $options[$connectionKey][$name]['driverOptions'] = $driverOptions + $parentDriverOptions; } } if (! empty($options['slaves']) || ! empty($options['replica'])) { $nonRewrittenKeys = [ 'driver' => true, 'driverClass' => true, 'wrapperClass' => true, 'keepSlave' => true, 'keepReplica' => true, 'shardChoser' => true, 'platform' => true, 'slaves' => true, 'primary' => true, 'replica' => true, 'shards' => true, 'serverVersion' => true, 'defaultTableOptions' => true, // included by safety but should have been unset already 'logging' => true, 'profiling' => true, 'mapping_types' => true, 'platform_service' => true, ]; foreach ($options as $key => $value) { if (isset($nonRewrittenKeys[$key])) { continue; } $options['primary'][$key] = $value; unset($options[$key]); } if (empty($options['wrapperClass'])) { // Change the wrapper class only if user did not configure custom one. $options['wrapperClass'] = PrimaryReadReplicaConnection::class; } } else { unset($options['slaves'], $options['replica']); } if (! empty($options['shards'])) { $nonRewrittenKeys = [ 'driver' => true, 'driverOptions' => true, 'driverClass' => true, 'wrapperClass' => true, 'keepSlave' => true, 'keepReplica' => true, 'shardChoser' => true, 'platform' => true, 'slaves' => true, 'replica' => true, 'global' => true, 'shards' => true, 'serverVersion' => true, 'defaultTableOptions' => true, // included by safety but should have been unset already 'logging' => true, 'profiling' => true, 'mapping_types' => true, 'platform_service' => true, 'shardManagerClass' => true, ]; foreach ($options as $key => $value) { if (isset($nonRewrittenKeys[$key])) { continue; } $options['global'][$key] = $value; unset($options[$key]); } if (empty($options['wrapperClass'])) { // Change the wrapper class only if the user does not already forced using a custom one. $options['wrapperClass'] = PoolingShardConnection::class; } if (empty($options['shardManagerClass'])) { // Change the shard manager class only if the user does not already forced using a custom one. $options['shardManagerClass'] = PoolingShardManager::class; } } else { unset($options['shards']); } return $options; } /** * Loads the Doctrine ORM configuration. * * Usage example: * * <doctrine:orm id="mydm" connection="myconn" /> * * @param array<string, mixed> $config An array of configuration settings * @param ContainerBuilder $container A ContainerBuilder instance */ protected function ormLoad(array $config, ContainerBuilder $container) { if (! class_exists(UnitOfWork::class)) { throw new LogicException('To configure the ORM layer, you must first install the doctrine/orm package.'); } $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('orm.xml'); if (! (new ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) { $container->getDefinition('doctrine.orm.entity_manager.abstract') ->setFactory(['%doctrine.orm.entity_manager.class%', 'create']); } if (class_exists(AbstractType::class)) { $container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']); } // available in Symfony 5.4 and higher if (! class_exists(DoctrineDbalCacheAdapterSchemaSubscriber::class)) { $container->removeDefinition('doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber'); } // available in Symfony 5.1 and up to Symfony 5.4 (deprecated) if (! class_exists(PdoCacheAdapterDoctrineSchemaSubscriber::class)) { $container->removeDefinition('doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber'); } // available in Symfony 5.3 and higher if (! class_exists(RememberMeTokenProviderDoctrineSchemaSubscriber::class)) { $container->removeDefinition('doctrine.orm.listeners.doctrine_token_provider_schema_subscriber'); } if (! class_exists(UlidGenerator::class)) { $container->removeDefinition('doctrine.ulid_generator'); } if (! class_exists(UuidGenerator::class)) { $container->removeDefinition('doctrine.uuid_generator'); } // available in Symfony 6.2 and higher if (! class_exists(EntityValueResolver::class)) { $container->removeDefinition('doctrine.orm.entity_value_resolver'); $container->removeDefinition('doctrine.orm.entity_value_resolver.expression_language'); } else { if (! class_exists(ExpressionLanguage::class)) { $container->removeDefinition('doctrine.orm.entity_value_resolver.expression_language'); } $controllerResolverDefaults = []; if (! $config['controller_resolver']['enabled']) { $controllerResolverDefaults['disabled'] = true; } if (! $config['controller_resolver']['auto_mapping']) { $controllerResolverDefaults['mapping'] = []; } if ($config['controller_resolver']['evict_cache']) { $controllerResolverDefaults['evict_cache'] = true; } if ($controllerResolverDefaults) { $container->getDefinition('doctrine.orm.entity_value_resolver')->setArgument(2, (new Definition(MapEntity::class))->setArguments([ null, null, null, $controllerResolverDefaults['mapping'] ?? null, null, null, null, $controllerResolverDefaults['evict_cache'] ?? null, $controllerResolverDefaults['disabled'] ?? false, ])); } } // not available in Doctrine ORM 3.0 and higher if (! class_exists(ConvertMappingCommand::class)) { $container->removeDefinition('doctrine.mapping_convert_command'); } if (! class_exists(EnsureProductionSettingsCommand::class)) { $container->removeDefinition('doctrine.ensure_production_settings_command'); } if (! class_exists(ClassMetadataExporter::class)) { $container->removeDefinition('doctrine.mapping_import_command'); } $entityManagers = []; foreach (array_keys($config['entity_managers']) as $name) { $entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name); } $container->setParameter('doctrine.entity_managers', $entityManagers); if (empty($config['default_entity_manager'])) { $tmp = array_keys($entityManagers); $config['default_entity_manager'] = reset($tmp); } $container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']); $options = ['auto_generate_proxy_classes', 'proxy_dir', 'proxy_namespace']; foreach ($options as $key) { $container->setParameter('doctrine.orm.' . $key, $config[$key]); } $container->setAlias('doctrine.orm.entity_manager', $defaultEntityManagerDefinitionId = sprintf('doctrine.orm.%s_entity_manager', $config['default_entity_manager'])); $container->getAlias('doctrine.orm.entity_manager')->setPublic(true); $config['entity_managers'] = $this->fixManagersAutoMappings($config['entity_managers'], $container->getParameter('kernel.bundles')); foreach ($config['entity_managers'] as $name => $entityManager) { $entityManager['name'] = $name; $this->loadOrmEntityManager($entityManager, $container); if (interface_exists(PropertyInfoExtractorInterface::class)) { $this->loadPropertyInfoExtractor($name, $container); } if (! interface_exists(LoaderInterface::class)) { continue; } $this->loadValidatorLoader($name, $container); } if ($config['resolve_target_entities']) { $def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity'); foreach ($config['resolve_target_entities'] as $name => $implementation) { $def->addMethodCall('addResolveTargetEntity', [ $name, $implementation, [], ]); } $def->addTag('doctrine.event_subscriber'); } $container->registerForAutoconfiguration(ServiceEntityRepositoryInterface::class) ->addTag(ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG); $container->registerForAutoconfiguration(EventSubscriberInterface::class) ->addTag('doctrine.event_subscriber'); $container->registerForAutoconfiguration(AbstractIdGenerator::class) ->addTag(IdGeneratorPass::ID_GENERATOR_TAG); if (method_exists($container, 'registerAttributeForAutoconfiguration')) { $container->registerAttributeForAutoconfiguration(AsEntityListener::class, static function (ChildDefinition $definition, AsEntityListener $attribute) { $definition->addTag('doctrine.orm.entity_listener', [ 'event' => $attribute->event, 'method' => $attribute->method, 'lazy' => $attribute->lazy, 'entity_manager' => $attribute->entityManager, 'entity' => $attribute->entity, ]); }); } /** @see DoctrineBundle::boot() */ $container->getDefinition($defaultEntityManagerDefinitionId) ->addTag('container.preload', [ 'class' => Autoloader::class, ]); } /** * Loads a configured ORM entity manager. * * @param array<string, mixed> $entityManager A configured ORM entity manager. * @param ContainerBuilder $container A ContainerBuilder instance */ protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container) { $ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration')); $ormConfigDef->addTag(IdGeneratorPass::CONFIGURATION_TAG); $this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container); $this->loadOrmCacheDrivers($entityManager, $container); if (isset($entityManager['entity_listener_resolver']) && $entityManager['entity_listener_resolver']) { $container->setAlias(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $entityManager['entity_listener_resolver']); } else { $definition = new Definition('%doctrine.orm.entity_listener_resolver.class%'); $definition->addArgument(new Reference('service_container')); $container->setDefinition(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name']), $definition); } $methods = [ 'setMetadataCache' => new Reference(sprintf('doctrine.orm.%s_metadata_cache', $entityManager['name'])), 'setQueryCache' => new Reference(sprintf('doctrine.orm.%s_query_cache', $entityManager['name'])), 'setResultCache' => new Reference(sprintf('doctrine.orm.%s_result_cache', $entityManager['name'])), 'setMetadataDriverImpl' => new Reference('doctrine.orm.' . $entityManager['name'] . '_metadata_driver'), 'setProxyDir' => '%doctrine.orm.proxy_dir%', 'setProxyNamespace' => '%doctrine.orm.proxy_namespace%', 'setAutoGenerateProxyClasses' => '%doctrine.orm.auto_generate_proxy_classes%', 'setSchemaIgnoreClasses' => $entityManager['schema_ignore_classes'], 'setClassMetadataFactoryName' => $entityManager['class_metadata_factory_name'], 'setDefaultRepositoryClassName' => $entityManager['default_repository_class'], 'setNamingStrategy' => new Reference($entityManager['naming_strategy']), 'setQuoteStrategy' => new Reference($entityManager['quote_strategy']), 'setEntityListenerResolver' => new Reference(sprintf('doctrine.orm.%s_entity_listener_resolver', $entityManager['name'])), ]; $listenerId = sprintf('doctrine.orm.%s_listeners.attach_entity_listeners', $entityManager['name']); $listenerDef = $container->setDefinition($listenerId, new Definition('%doctrine.orm.listeners.attach_entity_listeners.class%')); $listenerTagParams = ['event' => 'loadClassMetadata']; if (isset($entityManager['connection'])) { $listenerTagParams['connection'] = $entityManager['connection']; } $listenerDef->addTag('doctrine.event_listener', $listenerTagParams); if (isset($entityManager['second_level_cache'])) { $this->loadOrmSecondLevelCache($entityManager, $ormConfigDef, $container); } if ($entityManager['repository_factory']) { $methods['setRepositoryFactory'] = new Reference($entityManager['repository_factory']); } foreach ($methods as $method => $arg) { $ormConfigDef->addMethodCall($method, [$arg]); } foreach ($entityManager['hydrators'] as $name => $class) { $ormConfigDef->addMethodCall('addCustomHydrationMode', [$name, $class]); } if (! empty($entityManager['dql'])) { foreach ($entityManager['dql']['string_functions'] as $name => $function) { $ormConfigDef->addMethodCall('addCustomStringFunction', [$name, $function]); } foreach ($entityManager['dql']['numeric_functions'] as $name => $function) { $ormConfigDef->addMethodCall('addCustomNumericFunction', [$name, $function]); } foreach ($entityManager['dql']['datetime_functions'] as $name => $function) { $ormConfigDef->addMethodCall('addCustomDatetimeFunction', [$name, $function]); } } $enabledFilters = []; $filtersParameters = []; foreach ($entityManager['filters'] as $name => $filter) { $ormConfigDef->addMethodCall('addFilter', [$name, $filter['class']]); if ($filter['enabled']) { $enabledFilters[] = $name; } if (! $filter['parameters']) { continue; } $filtersParameters[$name] = $filter['parameters']; } $managerConfiguratorName = sprintf('doctrine.orm.%s_manager_configurator', $entityManager['name']); $container ->setDefinition($managerConfiguratorName, new ChildDefinition('doctrine.orm.manager_configurator.abstract')) ->replaceArgument(0, $enabledFilters) ->replaceArgument(1, $filtersParameters); if (! isset($entityManager['connection'])) { $entityManager['connection'] = $this->defaultConnection; } $entityManagerId = sprintf('doctrine.orm.%s_entity_manager', $entityManager['name']); $container ->setDefinition($entityManagerId, new ChildDefinition('doctrine.orm.entity_manager.abstract')) ->setPublic(true) ->setArguments([ new Reference(sprintf('doctrine.dbal.%s_connection', $entityManager['connection'])), new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])), ]) ->setConfigurator([new Reference($managerConfiguratorName), 'configure']); $container ->registerAliasForArgument($entityManagerId, EntityManagerInterface::class, sprintf('%sEntityManager', $entityManager['name'])) ->setPublic(false); $container->setAlias( sprintf('doctrine.orm.%s_entity_manager.event_manager', $entityManager['name']), new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $entityManager['connection']), false) ); if (! isset($entityManager['entity_listeners'])) { return; } $entities = $entityManager['entity_listeners']['entities']; foreach ($entities as $entityListenerClass => $entity) { foreach ($entity['listeners'] as $listenerClass => $listener) { foreach ($listener['events'] as $listenerEvent) { $listenerEventName = $listenerEvent['type']; $listenerMethod = $listenerEvent['method']; $listenerDef->addMethodCall('addEntityListener', [ $entityListenerClass, $listenerClass, $listenerEventName, $listenerMethod, ]); } } } } /** * Loads an ORM entity managers bundle mapping information. * * There are two distinct configuration possibilities for mapping information: * * 1. Specify a bundle and optionally details where the entity and mapping information reside. * 2. Specify an arbitrary mapping location. * * @param array<string, mixed> $entityManager A configured ORM entity manager * @param Definition $ormConfigDef A Definition instance * @param ContainerBuilder $container A ContainerBuilder instance * * @example * * doctrine.orm: * mappings: * MyBundle1: ~ * MyBundle2: yml * MyBundle3: { type: annotation, dir: Entities/ } * MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping } * MyBundle5: { type: attribute, dir: Entities/ } * MyBundle6: * type: yml * dir: bundle-mappings/ * alias: BundleAlias * arbitrary_key: * type: xml * dir: %kernel.project_dir%/src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Entities * prefix: DoctrineExtensions\Entities\ * alias: DExt * * In the case of bundles everything is really optional (which leads to autodetection for this bundle) but * in the mappings key everything except alias is a required argument. */ protected function loadOrmEntityManagerMappingInformation(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container) { // reset state of drivers and alias map. They are only used by this methods and children. $this->drivers = []; $this->aliasMap = []; $this->loadMappingInformation($entityManager, $container); $this->registerMappingDrivers($entityManager, $container); $ormConfigDef->addMethodCall('setEntityNamespaces', [$this->aliasMap]); } /** * Loads an ORM second level cache bundle mapping information. * * @param array<string, mixed> $entityManager A configured ORM entity manager * @param Definition $ormConfigDef A Definition instance * @param ContainerBuilder $container A ContainerBuilder instance * * @example * entity_managers: * default: * second_level_cache: * region_lifetime: 3600 * region_lock_lifetime: 60 * region_cache_driver: apc * log_enabled: true * regions: * my_service_region: * type: service * service : "my_service_region" * * my_query_region: * lifetime: 300 * cache_driver: array * type: filelock * * my_entity_region: * lifetime: 600 * cache_driver: * type: apc */ protected function loadOrmSecondLevelCache(array $entityManager, Definition $ormConfigDef, ContainerBuilder $container) { $driverId = null; $enabled = $entityManager['second_level_cache']['enabled']; if (isset($entityManager['second_level_cache']['region_cache_driver'])) { $driverName = 'second_level_cache.region_cache_driver'; $driverMap = $entityManager['second_level_cache']['region_cache_driver']; $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container); } $configId = sprintf('doctrine.orm.%s_second_level_cache.cache_configuration', $entityManager['name']); $regionsId = sprintf('doctrine.orm.%s_second_level_cache.regions_configuration', $entityManager['name']); $driverId = $driverId ?: sprintf('doctrine.orm.%s_second_level_cache.region_cache_driver', $entityManager['name']); $configDef = $container->setDefinition($configId, new Definition('%doctrine.orm.second_level_cache.cache_configuration.class%')); $regionsDef = $container ->setDefinition($regionsId, new Definition('%doctrine.orm.second_level_cache.regions_configuration.class%')) ->setArguments([$entityManager['second_level_cache']['region_lifetime'], $entityManager['second_level_cache']['region_lock_lifetime']]); $slcFactoryId = sprintf('doctrine.orm.%s_second_level_cache.default_cache_factory', $entityManager['name']); $factoryClass = $entityManager['second_level_cache']['factory'] ?? '%doctrine.orm.second_level_cache.default_cache_factory.class%'; $definition = new Definition($factoryClass, [new Reference($regionsId), new Reference($driverId)]); $slcFactoryDef = $container ->setDefinition($slcFactoryId, $definition); if (isset($entityManager['second_level_cache']['regions'])) { foreach ($entityManager['second_level_cache']['regions'] as $name => $region) { $regionRef = null; $regionType = $region['type']; if ($regionType === 'service') { $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name); $regionRef = new Reference($region['service']); $container->setAlias($regionId, new Alias($region['service'], false)); } if ($regionType === 'default' || $regionType === 'filelock') { $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s', $entityManager['name'], $name); $driverName = sprintf('second_level_cache.region.%s_driver', $name); $driverMap = $region['cache_driver']; $driverId = $this->loadCacheDriver($driverName, $entityManager['name'], $driverMap, $container); $regionRef = new Reference($regionId); $container ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.default_region.class%')) ->setArguments([$name, new Reference($driverId), $region['lifetime']]); } if ($regionType === 'filelock') { $regionId = sprintf('doctrine.orm.%s_second_level_cache.region.%s_filelock', $entityManager['name'], $name); $container ->setDefinition($regionId, new Definition('%doctrine.orm.second_level_cache.filelock_region.class%')) ->setArguments([$regionRef, $region['lock_path'], $region['lock_lifetime']]); $regionRef = new Reference($regionId); $regionsDef->addMethodCall('getLockLifetime', [$name, $region['lock_lifetime']]); } $regionsDef->addMethodCall('setLifetime', [$name, $region['lifetime']]); $slcFactoryDef->addMethodCall('setRegion', [$regionRef]); } } if ($entityManager['second_level_cache']['log_enabled']) { $loggerChainId = sprintf('doctrine.orm.%s_second_level_cache.logger_chain', $entityManager['name']); $loggerStatsId = sprintf('doctrine.orm.%s_second_level_cache.logger_statistics', $entityManager['name']); $loggerChaingDef = $container->setDefinition($loggerChainId, new Definition('%doctrine.orm.second_level_cache.logger_chain.class%')); $loggerStatsDef = $container->setDefinition($loggerStatsId, new Definition('%doctrine.orm.second_level_cache.logger_statistics.class%')); $loggerChaingDef->addMethodCall('setLogger', ['statistics', $loggerStatsDef]); $configDef->addMethodCall('setCacheLogger', [$loggerChaingDef]); foreach ($entityManager['second_level_cache']['loggers'] as $name => $logger) { $loggerId = sprintf('doctrine.orm.%s_second_level_cache.logger.%s', $entityManager['name'], $name); $loggerRef = new Reference($logger['service']); $container->setAlias($loggerId, new Alias($logger['service'], false)); $loggerChaingDef->addMethodCall('setLogger', [$name, $loggerRef]); } } $configDef->addMethodCall('setCacheFactory', [$slcFactoryDef]); $configDef->addMethodCall('setRegionsConfiguration', [$regionsDef]); $ormConfigDef->addMethodCall('setSecondLevelCacheEnabled', [$enabled]); $ormConfigDef->addMethodCall('setSecondLevelCacheConfiguration', [$configDef]); } /** * {@inheritDoc} */ protected function getObjectManagerElementName($name): string { return 'doctrine.orm.' . $name; } protected function getMappingObjectDefaultName(): string { return 'Entity'; } protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string { if ($bundleDir !== null && is_dir($bundleDir . '/config/doctrine')) { return 'config/doctrine'; } return 'Resources/config/doctrine'; } protected function getMappingResourceExtension(): string { return 'orm'; } /** * {@inheritDoc} */ protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container): string { $aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, $cacheName)); switch ($cacheDriver['type'] ?? 'pool') { case 'service': $serviceId = $cacheDriver['id']; break; case 'pool': $serviceId = $cacheDriver['pool'] ?? $this->createArrayAdapterCachePool($container, $objectManagerName, $cacheName); break; default: throw new \InvalidArgumentException(sprintf( 'Unknown cache of type "%s" configured for cache "%s" in entity manager "%s".', $cacheDriver['type'], $cacheName, $objectManagerName )); } $container->setAlias($aliasId, new Alias($serviceId, false)); return $aliasId; } /** * Loads a configured entity managers cache drivers. * * @param array<string, mixed> $entityManager A configured ORM entity manager. */ protected function loadOrmCacheDrivers(array $entityManager, ContainerBuilder $container) { if (isset($entityManager['metadata_cache_driver'])) { $this->loadCacheDriver('metadata_cache', $entityManager['name'], $entityManager['metadata_cache_driver'], $container); } else { $this->createMetadataCache($entityManager['name'], $container); } $this->loadCacheDriver('result_cache', $entityManager['name'], $entityManager['result_cache_driver'], $container); $this->loadCacheDriver('query_cache', $entityManager['name'], $entityManager['query_cache_driver'], $container); } private function createMetadataCache(string $objectManagerName, ContainerBuilder $container): void { $aliasId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache')); $cacheId = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, 'metadata'); $cache = new Definition(ArrayAdapter::class); if (! $container->getParameter('kernel.debug')) { $phpArrayFile = '%kernel.cache_dir%' . sprintf('/doctrine/orm/%s_metadata.php', $objectManagerName); $cacheWarmerServiceId = $this->getObjectManagerElementName(sprintf('%s_%s', $objectManagerName, 'metadata_cache_warmer')); $container->register($cacheWarmerServiceId, DoctrineMetadataCacheWarmer::class) ->setArguments([new Reference(sprintf('doctrine.orm.%s_entity_manager', $objectManagerName)), $phpArrayFile]) ->addTag('kernel.cache_warmer', ['priority' => 1000]); // priority should be higher than ProxyCacheWarmer $cache = new Definition(PhpArrayAdapter::class, [$phpArrayFile, $cache]); } $container->setDefinition($cacheId, $cache); $container->setAlias($aliasId, $cacheId); } /** * Loads a property info extractor for each defined entity manager. */ private function loadPropertyInfoExtractor(string $entityManagerName, ContainerBuilder $container): void { $propertyExtractorDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.property_info_extractor', $entityManagerName), DoctrineExtractor::class); $argumentId = sprintf('doctrine.orm.%s_entity_manager', $entityManagerName); $propertyExtractorDefinition->addArgument(new Reference($argumentId)); $propertyExtractorDefinition->addTag('property_info.list_extractor', ['priority' => -1001]); $propertyExtractorDefinition->addTag('property_info.type_extractor', ['priority' => -999]); $propertyExtractorDefinition->addTag('property_info.access_extractor', ['priority' => -999]); } /** * Loads a validator loader for each defined entity manager. */ private function loadValidatorLoader(string $entityManagerName, ContainerBuilder $container): void { $validatorLoaderDefinition = $container->register(sprintf('doctrine.orm.%s_entity_manager.validator_loader', $entityManagerName), DoctrineLoader::class); $validatorLoaderDefinition->addArgument(new Reference(sprintf('doctrine.orm.%s_entity_manager', $entityManagerName))); $validatorLoaderDefinition->addTag('validator.auto_mapper', ['priority' => -100]); } /** * @param array<string, mixed> $objectManager * @param string $cacheName * * @psalm-suppress MoreSpecificImplementedParamType */ public function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) { $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName . '_driver'], $container); } public function getXsdValidationBasePath(): string { return __DIR__ . '/../Resources/config/schema'; } public function getNamespace(): string { return 'http://symfony.com/schema/dic/doctrine'; } /** * {@inheritDoc} */ public function getConfiguration(array $config, ContainerBuilder $container): Configuration { return new Configuration((bool) $container->getParameter('kernel.debug')); } protected function getMetadataDriverClass(string $driverType): string { return '%' . $this->getObjectManagerElementName('metadata.' . $driverType . '.class') . '%'; } private function loadMessengerServices(ContainerBuilder $container): void { // If the Messenger component is installed and the doctrine transaction middleware is available, wire it: /** @psalm-suppress UndefinedClass Optional dependency */ if (! interface_exists(MessageBusInterface::class) || ! class_exists(DoctrineTransactionMiddleware::class)) { return; } $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('messenger.xml'); if (! class_exists(DoctrineClearEntityManagerWorkerSubscriber::class)) { $container->removeDefinition('doctrine.orm.messenger.event_subscriber.doctrine_clear_entity_manager'); } // available in Symfony 5.1 and higher if (! class_exists(MessengerTransportDoctrineSchemaSubscriber::class)) { $container->removeDefinition('doctrine.orm.messenger.doctrine_schema_subscriber'); } $transportFactoryDefinition = $container->getDefinition('messenger.transport.doctrine.factory'); if (! class_exists(DoctrineTransportFactory::class)) { // If symfony/messenger < 5.1 if (! class_exists(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class)) { // Dont add the tag return; } $transportFactoryDefinition->setClass(\Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory::class); } $transportFactoryDefinition->addTag('messenger.transport_factory'); } private function createArrayAdapterCachePool(ContainerBuilder $container, string $objectManagerName, string $cacheName): string { $id = sprintf('cache.doctrine.orm.%s.%s', $objectManagerName, str_replace('_cache', '', $cacheName)); $poolDefinition = $container->register($id, ArrayAdapter::class); $poolDefinition->addTag('cache.pool'); $container->setDefinition($id, $poolDefinition); return $id; } /** * @param string[] $connWithLogging * @param string[] $connWithProfiling * @param string[] $connWithBacktrace */ private function useMiddlewaresIfAvailable( ContainerBuilder $container, array $connWithLogging, array $connWithProfiling, array $connWithBacktrace ): void { /** @psalm-suppress UndefinedClass */ if (! interface_exists(MiddlewareInterface::class)) { return; } $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('middlewares.xml'); $container ->getDefinition('doctrine.dbal.logger') ->replaceArgument(0, null); $loggingMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.logging_middleware'); foreach ($connWithLogging as $connName) { $loggingMiddlewareAbstractDef->addTag('doctrine.middleware', ['connection' => $connName]); } if ($this->isSfDebugMiddlewareAvailable()) { $container->getDefinition('doctrine.debug_data_holder')->replaceArgument(0, $connWithBacktrace); $debugMiddlewareAbstractDef = $container->getDefinition('doctrine.dbal.debug_middleware'); foreach ($connWithProfiling as $connName) { $debugMiddlewareAbstractDef ->addTag('doctrine.middleware', ['connection' => $connName]); } } else { $container->removeDefinition('doctrine.dbal.debug_middleware'); $container->removeDefinition('doctrine.debug_data_holder'); } } private function isSfDebugMiddlewareAvailable(): bool { /** @psalm-suppress UndefinedClass */ return interface_exists(MiddlewareInterface::class) && class_exists(SfDebugMiddleware::class); } } doctrine-bundle/EventSubscriber/EventSubscriberInterface.php 0000644 00000000247 15120025743 0020370 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\EventSubscriber; use Doctrine\Common\EventSubscriber; interface EventSubscriberInterface extends EventSubscriber { } doctrine-bundle/Mapping/ClassMetadataCollection.php 0000644 00000001725 15120025743 0016454 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; class ClassMetadataCollection { /** @var string */ private $path; /** @var string */ private $namespace; /** @var ClassMetadata[] */ private $metadata; /** @param ClassMetadata[] $metadata */ public function __construct(array $metadata) { $this->metadata = $metadata; } /** @return ClassMetadata[] */ public function getMetadata() { return $this->metadata; } /** @param string $path */ public function setPath($path) { $this->path = $path; } /** @return string */ public function getPath() { return $this->path; } /** @param string $namespace */ public function setNamespace($namespace) { $this->namespace = $namespace; } /** @return string */ public function getNamespace() { return $this->namespace; } } doctrine-bundle/Mapping/ClassMetadataFactory.php 0000644 00000002067 15120025743 0015770 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use Doctrine\ORM\Id\AbstractIdGenerator; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory as BaseClassMetadataFactory; use function assert; class ClassMetadataFactory extends BaseClassMetadataFactory { /** * {@inheritDoc} */ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents): void { parent::doLoadMetadata($class, $parent, $rootEntityFound, $nonSuperclassParents); $customGeneratorDefinition = $class->customGeneratorDefinition; if (! isset($customGeneratorDefinition['instance'])) { return; } assert($customGeneratorDefinition['instance'] instanceof AbstractIdGenerator); $class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM); $class->setIdGenerator($customGeneratorDefinition['instance']); unset($customGeneratorDefinition['instance']); $class->setCustomGeneratorDefinition($customGeneratorDefinition); } } doctrine-bundle/Mapping/ContainerEntityListenerResolver.php 0000644 00000005044 15120025743 0020277 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use InvalidArgumentException; use Psr\Container\ContainerInterface; use RuntimeException; use function get_class; use function gettype; use function is_object; use function sprintf; use function trim; /** @final */ class ContainerEntityListenerResolver implements EntityListenerServiceResolver { /** @var ContainerInterface */ private $container; /** @var object[] Map to store entity listener instances. */ private $instances = []; /** @var string[] Map to store registered service ids */ private $serviceIds = []; /** @param ContainerInterface $container a service locator for listeners */ public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} */ public function clear($className = null) { if ($className === null) { $this->instances = []; return; } $className = $this->normalizeClassName($className); unset($this->instances[$className]); } /** * {@inheritdoc} */ public function register($object) { if (! is_object($object)) { throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object))); } $className = $this->normalizeClassName(get_class($object)); $this->instances[$className] = $object; } /** * {@inheritdoc} */ public function registerService($className, $serviceId) { $this->serviceIds[$this->normalizeClassName($className)] = $serviceId; } /** * {@inheritdoc} */ public function resolve($className) { $className = $this->normalizeClassName($className); if (! isset($this->instances[$className])) { if (isset($this->serviceIds[$className])) { $this->instances[$className] = $this->resolveService($this->serviceIds[$className]); } else { $this->instances[$className] = new $className(); } } return $this->instances[$className]; } /** @return object */ private function resolveService(string $serviceId) { if (! $this->container->has($serviceId)) { throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId)); } return $this->container->get($serviceId); } private function normalizeClassName(string $className): string { return trim($className, '\\'); } } doctrine-bundle/Mapping/DisconnectedMetadataFactory.php 0000644 00000014213 15120025743 0017321 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory; use Doctrine\Persistence\ManagerRegistry; use ReflectionClass; use RuntimeException; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use function array_pop; use function class_exists; use function dirname; use function explode; use function implode; use function sprintf; use function str_replace; use function strpos; /** * This class provides methods to access Doctrine entity class metadata for a * given bundle, namespace or entity class, for generation purposes */ class DisconnectedMetadataFactory { /** @var ManagerRegistry */ private $registry; /** @param ManagerRegistry $registry A ManagerRegistry instance */ public function __construct(ManagerRegistry $registry) { $this->registry = $registry; } /** * Gets the metadata of all classes of a bundle. * * @param BundleInterface $bundle A BundleInterface instance * * @return ClassMetadataCollection A ClassMetadataCollection instance * * @throws RuntimeException When bundle does not contain mapped entities. */ public function getBundleMetadata(BundleInterface $bundle) { $namespace = $bundle->getNamespace(); $metadata = $this->getMetadataForNamespace($namespace); if (! $metadata->getMetadata()) { throw new RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName())); } $path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath()); $metadata->setPath($path); $metadata->setNamespace($bundle->getNamespace()); return $metadata; } /** * Gets the metadata of a class. * * @param string $class A class name * @param string $path The path where the class is stored (if known) * * @return ClassMetadataCollection A ClassMetadataCollection instance * * @throws MappingException When class is not valid entity or mapped superclass. */ public function getClassMetadata($class, $path = null) { $metadata = $this->getMetadataForClass($class); if (! $metadata->getMetadata()) { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class); } $this->findNamespaceAndPathForMetadata($metadata, $path); return $metadata; } /** * Gets the metadata of all classes of a namespace. * * @param string $namespace A namespace name * @param string $path The path where the class is stored (if known) * * @return ClassMetadataCollection A ClassMetadataCollection instance * * @throws RuntimeException When namespace not contain mapped entities. */ public function getNamespaceMetadata($namespace, $path = null) { $metadata = $this->getMetadataForNamespace($namespace); if (! $metadata->getMetadata()) { throw new RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace)); } $this->findNamespaceAndPathForMetadata($metadata, $path); return $metadata; } /** * Find and configure path and namespace for the metadata collection. * * @param string|null $path * * @throws RuntimeException When unable to determine the path. */ public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null) { $all = $metadata->getMetadata(); if (class_exists($all[0]->name)) { $r = new ReflectionClass($all[0]->name); $path = $this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename())); $ns = $r->getNamespaceName(); } elseif ($path) { // Get namespace by removing the last component of the FQCN $nsParts = explode('\\', $all[0]->name); array_pop($nsParts); $ns = implode('\\', $nsParts); } else { throw new RuntimeException(sprintf('Unable to determine where to save the "%s" class (use the --path option).', $all[0]->name)); } $metadata->setPath($path); $metadata->setNamespace($ns); } /** * Get a base path for a class * * @throws RuntimeException When base path not found. */ private function getBasePathForClass(string $name, string $namespace, string $path): string { $namespace = str_replace('\\', '/', $namespace); $search = str_replace('\\', '/', $path); $destination = str_replace('/' . $namespace, '', $search, $c); if ($c !== 1) { throw new RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination)); } return $destination; } private function getMetadataForNamespace(string $namespace): ClassMetadataCollection { $metadata = []; foreach ($this->getAllMetadata() as $m) { if (strpos($m->name, $namespace) !== 0) { continue; } $metadata[] = $m; } return new ClassMetadataCollection($metadata); } private function getMetadataForClass(string $entity): ClassMetadataCollection { foreach ($this->registry->getManagers() as $em) { $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); if (! $cmf->isTransient($entity)) { return new ClassMetadataCollection([$cmf->getMetadataFor($entity)]); } } return new ClassMetadataCollection([]); } /** @return ClassMetadata[] */ private function getAllMetadata(): array { $metadata = []; foreach ($this->registry->getManagers() as $em) { $cmf = new DisconnectedClassMetadataFactory(); $cmf->setEntityManager($em); foreach ($cmf->getAllMetadata() as $m) { $metadata[] = $m; } } return $metadata; } } doctrine-bundle/Mapping/EntityListenerServiceResolver.php 0000644 00000000530 15120025743 0017750 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use Doctrine\ORM\Mapping\EntityListenerResolver; interface EntityListenerServiceResolver extends EntityListenerResolver { /** * @param string $className * @param string $serviceId */ // phpcs:ignore public function registerService($className, $serviceId); } doctrine-bundle/Mapping/MappingDriver.php 0000644 00000003672 15120025743 0014504 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Mapping; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface; use Psr\Container\ContainerInterface; class MappingDriver implements MappingDriverInterface { /** @var MappingDriverInterface */ private $driver; /** @var ContainerInterface */ private $idGeneratorLocator; public function __construct(MappingDriverInterface $driver, ContainerInterface $idGeneratorLocator) { $this->driver = $driver; $this->idGeneratorLocator = $idGeneratorLocator; } /** * {@inheritDoc} */ public function getAllClassNames() { return $this->driver->getAllClassNames(); } /** * {@inheritDoc} */ public function isTransient($className): bool { return $this->driver->isTransient($className); } /** * {@inheritDoc} */ public function loadMetadataForClass($className, ClassMetadata $metadata): void { $this->driver->loadMetadataForClass($className, $metadata); if ( ! $metadata instanceof ClassMetadataInfo || $metadata->generatorType !== ClassMetadataInfo::GENERATOR_TYPE_CUSTOM || ! isset($metadata->customGeneratorDefinition['class']) || ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class']) ) { return; } $idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']); $metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); } /** * Returns the inner driver */ public function getDriver(): MappingDriverInterface { return $this->driver; } } doctrine-bundle/Middleware/BacktraceDebugDataHolder.php 0000644 00000004542 15120025743 0017172 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Middleware; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Query; use function array_slice; use function debug_backtrace; use function in_array; use const DEBUG_BACKTRACE_IGNORE_ARGS; /** @psalm-suppress MissingDependency */ class BacktraceDebugDataHolder extends DebugDataHolder { /** @var string[] */ private $connWithBacktraces; /** @var array<string, array<string, mixed>[]> */ private $backtraces = []; /** @param string[] $connWithBacktraces */ public function __construct(array $connWithBacktraces) { $this->connWithBacktraces = $connWithBacktraces; } public function reset(): void { parent::reset(); $this->backtraces = []; } public function addQuery(string $connectionName, Query $query): void { parent::addQuery($connectionName, $query); if (! in_array($connectionName, $this->connWithBacktraces, true)) { return; } // array_slice to skip middleware calls in the trace $this->backtraces[$connectionName][] = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 2); } /** @return array<string, array<string, mixed>[]> */ public function getData(): array { $dataWithBacktraces = []; $data = parent::getData(); foreach ($data as $connectionName => $dataForConn) { $dataWithBacktraces[$connectionName] = $this->getDataForConnection($connectionName, $dataForConn); } return $dataWithBacktraces; } /** * @param mixed[][] $dataForConn * * @return mixed[][] */ private function getDataForConnection(string $connectionName, array $dataForConn): array { $data = []; foreach ($dataForConn as $idx => $record) { $data[] = $this->addBacktracesIfAvailable($connectionName, $record, $idx); } return $data; } /** * @param mixed[] $record * * @return mixed[] */ private function addBacktracesIfAvailable(string $connectionName, array $record, int $idx): array { if (! isset($this->backtraces[$connectionName])) { return $record; } $record['backtrace'] = $this->backtraces[$connectionName][$idx]; return $record; } } doctrine-bundle/Middleware/ConnectionNameAwareInterface.php 0000644 00000000243 15120025743 0020107 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Middleware; interface ConnectionNameAwareInterface { public function setConnectionName(string $name): void; } doctrine-bundle/Middleware/DebugMiddleware.php 0000644 00000002034 15120025743 0015432 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Middleware; use Doctrine\DBAL\Driver as DriverInterface; use Doctrine\DBAL\Driver\Middleware; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Driver; use Symfony\Component\Stopwatch\Stopwatch; class DebugMiddleware implements Middleware, ConnectionNameAwareInterface { /** @var DebugDataHolder */ private $debugDataHolder; /** @var Stopwatch|null */ private $stopwatch; /** @var string */ private $connectionName = 'default'; public function __construct(DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch) { $this->debugDataHolder = $debugDataHolder; $this->stopwatch = $stopwatch; } public function setConnectionName(string $name): void { $this->connectionName = $name; } public function wrap(DriverInterface $driver): DriverInterface { return new Driver($driver, $this->debugDataHolder, $this->stopwatch, $this->connectionName); } } doctrine-bundle/Orm/ManagerRegistryAwareEntityManagerProvider.php 0000644 00000002230 15120025743 0021352 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Orm; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\Console\EntityManagerProvider; use Doctrine\Persistence\ManagerRegistry; use RuntimeException; use function get_class; use function sprintf; final class ManagerRegistryAwareEntityManagerProvider implements EntityManagerProvider { /** @var ManagerRegistry */ private $managerRegistry; public function __construct(ManagerRegistry $managerRegistry) { $this->managerRegistry = $managerRegistry; } public function getDefaultManager(): EntityManagerInterface { return $this->getManager($this->managerRegistry->getDefaultManagerName()); } public function getManager(string $name): EntityManagerInterface { $em = $this->managerRegistry->getManager($name); if ($em instanceof EntityManagerInterface) { return $em; } throw new RuntimeException( sprintf( 'Only managers of type "%s" are supported. Instance of "%s given.', EntityManagerInterface::class, get_class($em) ) ); } } doctrine-bundle/Repository/ContainerRepositoryFactory.php 0000644 00000007616 15120025743 0020075 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Repository; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\ObjectRepository; use Psr\Container\ContainerInterface; use RuntimeException; use function class_exists; use function is_a; use function spl_object_hash; use function sprintf; /** * Fetches repositories from the container or falls back to normal creation. */ final class ContainerRepositoryFactory implements RepositoryFactory { /** @var array<string, ObjectRepository> */ private $managedRepositories = []; /** @var ContainerInterface */ private $container; /** @param ContainerInterface $container A service locator containing the repositories */ public function __construct(ContainerInterface $container) { $this->container = $container; } /** * {@inheritdoc} * * @template T of object */ public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository { $metadata = $entityManager->getClassMetadata($entityName); $repositoryServiceId = $metadata->customRepositoryClassName; $customRepositoryName = $metadata->customRepositoryClassName; if ($customRepositoryName !== null) { // fetch from the container if ($this->container->has($customRepositoryName)) { $repository = $this->container->get($customRepositoryName); if (! $repository instanceof ObjectRepository) { throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository).', $repositoryServiceId)); } /** @psalm-var ObjectRepository<T> */ return $repository; } // if not in the container but the class/id implements the interface, throw an error if (is_a($customRepositoryName, ServiceEntityRepositoryInterface::class, true)) { throw new RuntimeException(sprintf('The "%s" entity repository implements "%s", but its service could not be found. Make sure the service exists and is tagged with "%s".', $customRepositoryName, ServiceEntityRepositoryInterface::class, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG)); } if (! class_exists($customRepositoryName)) { throw new RuntimeException(sprintf('The "%s" entity has a repositoryClass set to "%s", but this is not a valid class. Check your class naming. If this is meant to be a service id, make sure this service exists and is tagged with "%s".', $metadata->name, $customRepositoryName, ServiceRepositoryCompilerPass::REPOSITORY_SERVICE_TAG)); } // allow the repository to be created below } return $this->getOrCreateRepository($entityManager, $metadata); } /** * @param ClassMetadata<TEntity> $metadata * * @return ObjectRepository<TEntity> * * @template TEntity of object */ private function getOrCreateRepository( EntityManagerInterface $entityManager, ClassMetadata $metadata ): ObjectRepository { $repositoryHash = $metadata->getName() . spl_object_hash($entityManager); if (isset($this->managedRepositories[$repositoryHash])) { /** @psalm-var ObjectRepository<TEntity> */ return $this->managedRepositories[$repositoryHash]; } $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); /** @psalm-var ObjectRepository<TEntity> */ return $this->managedRepositories[$repositoryHash] = new $repositoryClassName($entityManager, $metadata); } } doctrine-bundle/Repository/ServiceEntityRepository.php 0000644 00000002672 15120025743 0017415 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Repository; use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ManagerRegistry; use LogicException; use function sprintf; /** * Optional EntityRepository base class with a simplified constructor (for autowiring). * * To use in your class, inject the "registry" service and call * the parent constructor. For example: * * class YourEntityRepository extends ServiceEntityRepository * { * public function __construct(ManagerRegistry $registry) * { * parent::__construct($registry, YourEntity::class); * } * } * * @template T of object * @template-extends EntityRepository<T> */ class ServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface { /** * @param string $entityClass The class name of the entity this repository manages * @psalm-param class-string<T> $entityClass */ public function __construct(ManagerRegistry $registry, string $entityClass) { $manager = $registry->getManagerForClass($entityClass); if ($manager === null) { throw new LogicException(sprintf( 'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.', $entityClass )); } parent::__construct($manager, $manager->getClassMetadata($entityClass)); } } doctrine-bundle/Repository/ServiceEntityRepositoryInterface.php 0000644 00000000310 15120025743 0021221 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Repository; /** * This interface signals that your repository should be loaded from the container. */ interface ServiceEntityRepositoryInterface { } doctrine-bundle/Resources/config/schema/doctrine-1.0.xsd 0000644 00000032030 15120025743 0017121 0 ustar 00 <?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns="http://symfony.com/schema/dic/doctrine" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://symfony.com/schema/dic/doctrine" elementFormDefault="qualified"> <xsd:element name="config"> <xsd:complexType> <xsd:all> <xsd:element name="dbal" type="dbal" minOccurs="0" maxOccurs="1" /> <xsd:element name="orm" type="orm" minOccurs="0" maxOccurs="1" /> </xsd:all> </xsd:complexType> </xsd:element> <xsd:complexType name="named_scalar"> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="name" type="xsd:string" use="required" /> </xsd:extension> </xsd:simpleContent> </xsd:complexType> <!-- DBAL configuration --> <xsd:attributeGroup name="connection-config"> <xsd:attribute name="driver" type="xsd:string" /> <xsd:attribute name="driver-class" type="xsd:string" /> <xsd:attribute name="wrapper-class" type="xsd:string" /> <xsd:attribute name="keep-slave" type="xsd:string" /> <xsd:attribute name="keep-replica" type="xsd:string" /> <xsd:attribute name="platform-service" type="xsd:string" /> <xsd:attribute name="shard-choser" type="xsd:string" /> <xsd:attribute name="shard-choser-service" type="xsd:string" /> <xsd:attribute name="auto-commit" type="xsd:string" /> <xsd:attribute name="schema-filter" type="xsd:string" /> <xsd:attribute name="logging" type="xsd:string" default="false" /> <xsd:attribute name="profiling" type="xsd:string" default="false" /> <xsd:attribute name="profiling-collect-backtrace" type="xsd:string" default="false" /> <xsd:attribute name="profiling-collect-schema-errors" type="xsd:string" default="true" /> <xsd:attribute name="server-version" type="xsd:string" /> <xsd:attribute name="use-savepoints" type="xsd:boolean" /> <xsd:attributeGroup ref="driver-config" /> </xsd:attributeGroup> <xsd:attributeGroup name="driver-config"> <xsd:attribute name="url" type="xsd:string" /> <xsd:attribute name="dbname" type="xsd:string" /> <xsd:attribute name="host" type="xsd:string" /> <xsd:attribute name="port" type="xsd:string" /> <xsd:attribute name="user" type="xsd:string" /> <xsd:attribute name="password" type="xsd:string" /> <xsd:attribute name="override-url" type="xsd:boolean" /> <xsd:attribute name="dbname-suffix" type="xsd:string" /> <xsd:attribute name="application-name" type="xsd:string" /> <xsd:attribute name="path" type="xsd:string" /> <xsd:attribute name="unix-socket" type="xsd:string" /> <xsd:attribute name="memory" type="xsd:string" /> <xsd:attribute name="charset" type="xsd:string" /> <xsd:attribute name="persistent" type="xsd:string" /> <xsd:attribute name="protocol" type="xsd:string" /> <xsd:attribute name="server" type="xsd:string" /> <xsd:attribute name="service" type="xsd:string" /> <xsd:attribute name="servicename" type="xsd:string" /> <xsd:attribute name="session-mode" type="xsd:string" /> <xsd:attribute name="default_dbname" type="xsd:string" /> <xsd:attribute name="sslmode" type="xsd:string" /> <xsd:attribute name="sslrootcert" type="xsd:string" /> <xsd:attribute name="sslcert" type="xsd:string" /> <xsd:attribute name="sslkey" type="xsd:string" /> <xsd:attribute name="sslcrl" type="xsd:string" /> <xsd:attribute name="pooled" type="xsd:string" /> <xsd:attribute name="multiple-active-result-sets" type="xsd:string" /> <xsd:attribute name="connectstring" type="xsd:string" /> <xsd:attribute name="instancename" type="xsd:string" /> </xsd:attributeGroup> <xsd:group name="connection-child-config"> <xsd:choice> <xsd:element name="option" type="option" /> <xsd:element name="mapping-type" type="named_scalar" /> <xsd:element name="slave" type="replica" /> <xsd:element name="replica" type="replica" /> <xsd:element name="shard" type="shard" /> <xsd:element name="default-table-option" type="named_scalar" /> </xsd:choice> </xsd:group> <xsd:complexType name="dbal"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="connection" type="connection" /> <xsd:element name="type" type="named_scalar" /> <xsd:group ref="connection-child-config" /> </xsd:choice> <xsd:attribute name="default-connection" type="xsd:string" /> <xsd:attributeGroup ref="connection-config" /> </xsd:complexType> <xsd:complexType name="option"> <xsd:complexContent> <xsd:extension base="xsd:anyType"> <xsd:attribute name="key" type="xsd:string" use="required" /> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="connection"> <xsd:group ref="connection-child-config" minOccurs="0" maxOccurs="unbounded" /> <xsd:attribute name="name" type="xsd:string" use="required" /> <xsd:attributeGroup ref="connection-config" /> </xsd:complexType> <xsd:complexType name="replica"> <xsd:attribute name="name" type="xsd:string" use="required" /> <xsd:attributeGroup ref="driver-config" /> </xsd:complexType> <xsd:complexType name="shard"> <xsd:attribute name="id" type="xsd:integer" use="required" /> <xsd:attributeGroup ref="driver-config" /> </xsd:complexType> <!-- ORM configuration --> <xsd:complexType name="mapping"> <xsd:attribute name="name" type="xsd:string" use="required" /> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="dir" type="xsd:string" /> <xsd:attribute name="alias" type="xsd:string" /> <xsd:attribute name="prefix" type="xsd:string" /> <xsd:attribute name="is-bundle" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="orm"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="entity-manager" type="entity_manager" /> <xsd:element name="resolve-target-entity" type="resolve_target_entity" minOccurs="0" maxOccurs="unbounded" /> <xsd:group ref="entity-manager-child-config" /> </xsd:choice> <xsd:attribute name="default-entity-manager" type="xsd:string" /> <xsd:attribute name="proxy-dir" type="xsd:string" /> <xsd:attribute name="proxy-namespace" type="xsd:string" /> <xsd:attribute name="auto-generate-proxy-classes" type="xsd:string" default="false" /> <xsd:attributeGroup ref="entity-manager-config" /> </xsd:complexType> <xsd:complexType name="resolve_target_entity"> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="interface" type="xsd:string" use="required" /> </xsd:extension> </xsd:simpleContent> </xsd:complexType> <xsd:simpleType name="cache_driver_type"> <xsd:restriction base="xsd:token"> <xsd:enumeration value="pool"/> <xsd:enumeration value="service"/> </xsd:restriction> </xsd:simpleType> <xsd:complexType name="cache_driver"> <xsd:attribute name="type" type="cache_driver_type" default="pool" /> <xsd:attribute name="id" type="xsd:string" /> <xsd:attribute name="pool" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="entity_listeners"> <xsd:choice minOccurs="1"> <xsd:element name="entity" type="entity_listeners_entity" minOccurs="1" maxOccurs="unbounded" /> </xsd:choice> </xsd:complexType> <xsd:complexType name="entity_listeners_entity"> <xsd:choice minOccurs="1"> <xsd:element name="listener" type="entity_listeners_listener" minOccurs="1" maxOccurs="unbounded" /> </xsd:choice> <xsd:attribute name="class" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="entity_listeners_listener"> <xsd:choice minOccurs="1"> <xsd:element name="event" type="entity_listeners_event" minOccurs="1" maxOccurs="unbounded" /> </xsd:choice> <xsd:attribute name="class" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="entity_listeners_event"> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="method" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="entity_manager"> <xsd:group ref="entity-manager-child-config" minOccurs="0" maxOccurs="unbounded" /> <xsd:attribute name="name" type="xsd:string" use="required" /> <xsd:attributeGroup ref="entity-manager-config" /> </xsd:complexType> <xsd:group name="entity-manager-child-config"> <xsd:choice> <xsd:element name="mapping" type="mapping" /> <xsd:element name="metadata-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" /> <xsd:element name="result-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" /> <xsd:element name="query-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" /> <xsd:element name="dql" type="dql" minOccurs="0" maxOccurs="1" /> <xsd:element name="hydrator" type="named_scalar" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="filter" type="filter" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="entity-listeners" type="entity_listeners" minOccurs="0" maxOccurs="1" /> <xsd:element name="second-level-cache" type="second-level-cache" minOccurs="0" maxOccurs="1" /> <xsd:element name="schema-ignore-class" type="xsd:string" minOccurs="0" maxOccurs="unbounded" /> </xsd:choice> </xsd:group> <xsd:attributeGroup name="entity-manager-config"> <xsd:attribute name="auto-mapping" type="xsd:string" /> <xsd:attribute name="connection" type="xsd:string" /> <xsd:attribute name="default-repository-class" type="xsd:string" /> <xsd:attribute name="class-metadata-factory-name" type="xsd:string" /> <xsd:attribute name="naming-strategy" type="xsd:string" /> <xsd:attribute name="quote-strategy" type="xsd:string" /> <xsd:attribute name="entity-listener-resolver" type="xsd:string" /> <xsd:attribute name="repository-factory" type="xsd:string" /> </xsd:attributeGroup> <xsd:complexType name="filter" mixed="true"> <xsd:choice minOccurs="0"> <xsd:element name="parameter" type="named_scalar" minOccurs="0" maxOccurs="unbounded" /> </xsd:choice> <xsd:attribute name="name" type="xsd:string" use="required" /> <xsd:attribute name="class" type="xsd:string" /> <xsd:attribute name="enabled" type="xsd:boolean" /> </xsd:complexType> <xsd:complexType name="second-level-cache-region" mixed="true"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" /> </xsd:choice> <xsd:attribute name="name" type="xsd:string" /> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="service" type="xsd:string" /> <xsd:attribute name="lifetime" type="xsd:integer" /> <xsd:attribute name="lock-lifetime" type="xsd:integer" /> <xsd:attribute name="cache-driver" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="second-level-cache-logger" mixed="true"> <xsd:attribute name="name" type="xsd:string" /> <xsd:attribute name="service" type="xsd:string" /> </xsd:complexType> <xsd:complexType name="second-level-cache" mixed="true"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="logger" type="second-level-cache-logger" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="region" type="second-level-cache-region" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="region-cache-driver" type="cache_driver" minOccurs="0" maxOccurs="1" /> </xsd:choice> <xsd:attribute name="enabled" type="xsd:boolean" default="true"/> <xsd:attribute name="log-enabled" type="xsd:boolean" default="true"/> <xsd:attribute name="factory" type="xsd:string" /> <xsd:attribute name="query-validator" type="xsd:string" /> <xsd:attribute name="region-lifetime" type="xsd:integer" /> <xsd:attribute name="region-lock-lifetime" type="xsd:integer" /> </xsd:complexType> <xsd:complexType name="dql"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="string-function" type="named_scalar" /> <xsd:element name="numeric-function" type="named_scalar" /> <xsd:element name="datetime-function" type="named_scalar" /> </xsd:choice> </xsd:complexType> </xsd:schema> doctrine-bundle/Resources/config/dbal.xml 0000644 00000015075 15120025743 0014474 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="doctrine.dbal.logger.chain.class">Doctrine\DBAL\Logging\LoggerChain</parameter> <parameter key="doctrine.dbal.logger.profiling.class">Doctrine\DBAL\Logging\DebugStack</parameter> <parameter key="doctrine.dbal.logger.class">Symfony\Bridge\Doctrine\Logger\DbalLogger</parameter> <parameter key="doctrine.dbal.configuration.class">Doctrine\DBAL\Configuration</parameter> <parameter key="doctrine.data_collector.class">Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector</parameter> <parameter key="doctrine.dbal.connection.event_manager.class">Symfony\Bridge\Doctrine\ContainerAwareEventManager</parameter> <parameter key="doctrine.dbal.connection_factory.class">Doctrine\Bundle\DoctrineBundle\ConnectionFactory</parameter> <parameter key="doctrine.dbal.events.mysql_session_init.class">Doctrine\DBAL\Event\Listeners\MysqlSessionInit</parameter> <parameter key="doctrine.dbal.events.oracle_session_init.class">Doctrine\DBAL\Event\Listeners\OracleSessionInit</parameter> <parameter key="doctrine.class">Doctrine\Bundle\DoctrineBundle\Registry</parameter> <parameter key="doctrine.entity_managers" type="collection"></parameter> <parameter key="doctrine.default_entity_manager"></parameter> </parameters> <services> <service id="Doctrine\DBAL\Driver\Connection" alias="database_connection" public="false" /> <service id="Doctrine\DBAL\Connection" alias="database_connection" public="false" /> <service id="doctrine.dbal.logger.chain" class="%doctrine.dbal.logger.chain.class%" public="false" abstract="true" > <!-- TODO: deprecate this service in 2.3.0 --> </service> <service id="doctrine.dbal.logger.profiling" class="%doctrine.dbal.logger.profiling.class%" public="false" abstract="true" /> <service id="doctrine.dbal.logger.backtrace" class="Doctrine\Bundle\DoctrineBundle\Dbal\Logging\BacktraceLogger" public="false" abstract="true" /> <service id="doctrine.dbal.logger" class="%doctrine.dbal.logger.class%" public="false"> <tag name="monolog.logger" channel="doctrine" /> <argument type="service" id="logger" on-invalid="null" /> <argument type="service" id="debug.stopwatch" on-invalid="null" /> </service> <service id="data_collector.doctrine" class="%doctrine.data_collector.class%" public="false"> <tag name="data_collector" template="@Doctrine/Collector/db.html.twig" id="db" priority="250" /> <argument type="service" id="doctrine" /> <argument>true</argument> <argument type="service" id="doctrine.debug_data_holder" on-invalid="null"/> </service> <service id="doctrine.dbal.connection_factory" class="%doctrine.dbal.connection_factory.class%"> <argument>%doctrine.dbal.connection_factory.types%</argument> </service> <service id="doctrine.dbal.connection" class="Doctrine\DBAL\Connection" abstract="true"> <factory service="doctrine.dbal.connection_factory" method="createConnection" /> </service> <service id="doctrine.dbal.connection.event_manager" class="%doctrine.dbal.connection.event_manager.class%" public="false" abstract="true"> <argument type="service" id="service_container" /> </service> <service id="doctrine.dbal.connection.configuration" class="%doctrine.dbal.configuration.class%" public="false" abstract="true" /> <service id="doctrine" class="%doctrine.class%" public="true"> <argument type="service" id="service_container" /> <argument>%doctrine.connections%</argument> <argument>%doctrine.entity_managers%</argument> <argument>%doctrine.default_connection%</argument> <argument>%doctrine.default_entity_manager%</argument> <tag name="kernel.reset" method="reset" /> </service> <service id="Doctrine\Persistence\ManagerRegistry" alias="doctrine" public="false" /> <service id="Doctrine\Common\Persistence\ManagerRegistry" alias="doctrine" public="false" /> <service id="doctrine.twig.doctrine_extension" class="Doctrine\Bundle\DoctrineBundle\Twig\DoctrineExtension" public="false"> <tag name="twig.extension" /> </service> <service id="doctrine.dbal.schema_asset_filter_manager" class="Doctrine\Bundle\DoctrineBundle\Dbal\SchemaAssetsFilterManager" public="false" abstract="true"> <!-- schema assets filters --> </service> <service id="doctrine.dbal.well_known_schema_asset_filter" class="Doctrine\Bundle\DoctrineBundle\Dbal\BlacklistSchemaAssetFilter" public="false"> <argument type="collection" /> </service> <!-- commands --> <service id="doctrine.database_create_command" class="Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand"> <argument type="service" id="doctrine" /> <tag name="console.command" command="doctrine:database:create" /> </service> <service id="doctrine.database_drop_command" class="Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand"> <argument type="service" id="doctrine" /> <tag name="console.command" command="doctrine:database:drop" /> </service> <service id="doctrine.query_sql_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\RunSqlDoctrineCommand"> <argument type="service" id="Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider" on-invalid="null" /> <tag name="console.command" command="doctrine:query:sql" /> </service> <service id="Doctrine\DBAL\Tools\Console\Command\RunSqlCommand"> <argument type="service" id="Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider" on-invalid="null" /> <tag name="console.command" command="dbal:run-sql" /> </service> <service id="Doctrine\Bundle\DoctrineBundle\Controller\ProfilerController"> <argument type="service" id="twig" /> <argument type="service" id="doctrine" /> <argument type="service" id="profiler" /> <tag name="controller.service_arguments" /> </service> </services> </container> doctrine-bundle/Resources/config/messenger.xml 0000644 00000006410 15120025743 0015553 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <!-- The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use the "doctrine_transaction" shortcut in message buses middleware config --> <service id="messenger.middleware.doctrine_transaction" class="Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware" abstract="true" public="false"> <argument type="service" id="doctrine" /> </service> <!-- The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use the "doctrine_ping_connection" shortcut in message buses middleware config --> <service id="messenger.middleware.doctrine_ping_connection" class="Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware" abstract="true" public="false"> <argument type="service" id="doctrine" /> </service> <!-- The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use the "doctrine_close_connection" shortcut in message buses middleware config --> <service id="messenger.middleware.doctrine_close_connection" class="Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware" abstract="true" public="false"> <argument type="service" id="doctrine" /> </service> <!-- The following service isn't prefixed by the "doctrine.orm" namespace in order for end-users to just use the "doctrine_open_transaction_logger" shortcut in message buses middleware config --> <service id="messenger.middleware.doctrine_open_transaction_logger" class="Symfony\Bridge\Doctrine\Messenger\DoctrineOpenTransactionLoggerMiddleware" abstract="true" public="false"> <argument type="service" id="doctrine" /> <argument>null</argument> <argument type="service" id="logger" /> </service> <!-- The following service isn't tagged as transport factory because the class may not exist. The tag is added conditionally in DoctrineExtension. --> <service id="messenger.transport.doctrine.factory" class="Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransportFactory" public="false"> <argument type="service" id="doctrine" /> </service> <service id="doctrine.orm.messenger.event_subscriber.doctrine_clear_entity_manager" class="Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber" public="false"> <tag name="kernel.event_subscriber" /> <argument type="service" id="doctrine" /> </service> <service id="doctrine.orm.messenger.doctrine_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber"> <argument type="tagged" tag="messenger.receiver" /> <tag name="doctrine.event_subscriber" /> </service> </services> </container> doctrine-bundle/Resources/config/middlewares.xml 0000644 00000002024 15120025743 0016060 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="doctrine.dbal.logging_middleware" class="Doctrine\DBAL\Logging\Middleware" abstract="true"> <argument type="service" id="logger" /> <tag name="monolog.logger" channel="doctrine" /> </service> <service id="doctrine.debug_data_holder" class="Doctrine\Bundle\DoctrineBundle\Middleware\BacktraceDebugDataHolder"> <argument type="collection" /> </service> <service id="doctrine.dbal.debug_middleware" class="Doctrine\Bundle\DoctrineBundle\Middleware\DebugMiddleware" abstract="true"> <argument type="service" id="doctrine.debug_data_holder" /> <argument type="service" id="debug.stopwatch" on-invalid="null" /> </service> </services> </container> doctrine-bundle/Resources/config/orm.xml 0000644 00000041101 15120025743 0014354 0 ustar 00 <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="doctrine.orm.configuration.class">Doctrine\ORM\Configuration</parameter> <parameter key="doctrine.orm.entity_manager.class">Doctrine\ORM\EntityManager</parameter> <parameter key="doctrine.orm.manager_configurator.class">Doctrine\Bundle\DoctrineBundle\ManagerConfigurator</parameter> <!-- cache --> <parameter key="doctrine.orm.cache.array.class">Doctrine\Common\Cache\ArrayCache</parameter> <parameter key="doctrine.orm.cache.apc.class">Doctrine\Common\Cache\ApcCache</parameter> <parameter key="doctrine.orm.cache.memcache.class">Doctrine\Common\Cache\MemcacheCache</parameter> <parameter key="doctrine.orm.cache.memcache_host">localhost</parameter> <parameter key="doctrine.orm.cache.memcache_port">11211</parameter> <parameter key="doctrine.orm.cache.memcache_instance.class">Memcache</parameter> <parameter key="doctrine.orm.cache.memcached.class">Doctrine\Common\Cache\MemcachedCache</parameter> <parameter key="doctrine.orm.cache.memcached_host">localhost</parameter> <parameter key="doctrine.orm.cache.memcached_port">11211</parameter> <parameter key="doctrine.orm.cache.memcached_instance.class">Memcached</parameter> <parameter key="doctrine.orm.cache.redis.class">Doctrine\Common\Cache\RedisCache</parameter> <parameter key="doctrine.orm.cache.redis_host">localhost</parameter> <parameter key="doctrine.orm.cache.redis_port">6379</parameter> <parameter key="doctrine.orm.cache.redis_instance.class">Redis</parameter> <parameter key="doctrine.orm.cache.xcache.class">Doctrine\Common\Cache\XcacheCache</parameter> <parameter key="doctrine.orm.cache.wincache.class">Doctrine\Common\Cache\WinCacheCache</parameter> <parameter key="doctrine.orm.cache.zenddata.class">Doctrine\Common\Cache\ZendDataCache</parameter> <!-- metadata --> <parameter key="doctrine.orm.metadata.driver_chain.class">Doctrine\Persistence\Mapping\Driver\MappingDriverChain</parameter> <parameter key="doctrine.orm.metadata.annotation.class">Doctrine\ORM\Mapping\Driver\AnnotationDriver</parameter> <parameter key="doctrine.orm.metadata.xml.class">Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver</parameter> <parameter key="doctrine.orm.metadata.yml.class">Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver</parameter> <parameter key="doctrine.orm.metadata.php.class">Doctrine\ORM\Mapping\Driver\PHPDriver</parameter> <parameter key="doctrine.orm.metadata.staticphp.class">Doctrine\ORM\Mapping\Driver\StaticPHPDriver</parameter> <parameter key="doctrine.orm.metadata.attribute.class">Doctrine\ORM\Mapping\Driver\AttributeDriver</parameter> <!-- cache warmer --> <parameter key="doctrine.orm.proxy_cache_warmer.class">Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer</parameter> <!-- form field factory guesser --> <parameter key="form.type_guesser.doctrine.class">Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser</parameter> <!-- validator --> <parameter key="doctrine.orm.validator.unique.class">Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator</parameter> <parameter key="doctrine.orm.validator_initializer.class">Symfony\Bridge\Doctrine\Validator\DoctrineInitializer</parameter> <!-- security --> <parameter key="doctrine.orm.security.user.provider.class">Symfony\Bridge\Doctrine\Security\User\EntityUserProvider</parameter> <!-- listeners --> <parameter key="doctrine.orm.listeners.resolve_target_entity.class">Doctrine\ORM\Tools\ResolveTargetEntityListener</parameter> <parameter key="doctrine.orm.listeners.attach_entity_listeners.class">Doctrine\ORM\Tools\AttachEntityListenersListener</parameter> <!-- naming strategy --> <parameter key="doctrine.orm.naming_strategy.default.class">Doctrine\ORM\Mapping\DefaultNamingStrategy</parameter> <parameter key="doctrine.orm.naming_strategy.underscore.class">Doctrine\ORM\Mapping\UnderscoreNamingStrategy</parameter> <!-- quote strategy --> <parameter key="doctrine.orm.quote_strategy.default.class">Doctrine\ORM\Mapping\DefaultQuoteStrategy</parameter> <parameter key="doctrine.orm.quote_strategy.ansi.class">Doctrine\ORM\Mapping\AnsiQuoteStrategy</parameter> <!-- entity listener resolver --> <parameter key="doctrine.orm.entity_listener_resolver.class">Doctrine\Bundle\DoctrineBundle\Mapping\ContainerEntityListenerResolver</parameter> <!-- second level cache --> <parameter key="doctrine.orm.second_level_cache.default_cache_factory.class">Doctrine\ORM\Cache\DefaultCacheFactory</parameter> <parameter key="doctrine.orm.second_level_cache.default_region.class">Doctrine\ORM\Cache\Region\DefaultRegion</parameter> <parameter key="doctrine.orm.second_level_cache.filelock_region.class">Doctrine\ORM\Cache\Region\FileLockRegion</parameter> <parameter key="doctrine.orm.second_level_cache.logger_chain.class">Doctrine\ORM\Cache\Logging\CacheLoggerChain</parameter> <parameter key="doctrine.orm.second_level_cache.logger_statistics.class">Doctrine\ORM\Cache\Logging\StatisticsCacheLogger</parameter> <parameter key="doctrine.orm.second_level_cache.cache_configuration.class">Doctrine\ORM\Cache\CacheConfiguration</parameter> <parameter key="doctrine.orm.second_level_cache.regions_configuration.class">Doctrine\ORM\Cache\RegionsConfiguration</parameter> </parameters> <services> <service id="Doctrine\ORM\EntityManagerInterface" alias="doctrine.orm.entity_manager" public="false" /> <!--- Internal Annotation Metadata Reader Service alias, use annotation_reader service --> <service id="doctrine.orm.metadata.annotation_reader" alias="annotation_reader" public="false" /> <service id="doctrine.orm.proxy_cache_warmer" class="%doctrine.orm.proxy_cache_warmer.class%" public="false"> <tag name="kernel.cache_warmer" /> <argument type="service" id="doctrine" /> </service> <service id="form.type_guesser.doctrine" class="%form.type_guesser.doctrine.class%"> <tag name="form.type_guesser" /> <argument type="service" id="doctrine" /> </service> <service id="form.type.entity" class="Symfony\Bridge\Doctrine\Form\Type\EntityType"> <tag name="form.type" alias="entity" /> <argument type="service" id="doctrine" /> </service> <service id="doctrine.orm.configuration" class="%doctrine.orm.configuration.class%" abstract="true" public="false" /> <service id="doctrine.orm.entity_manager.abstract" class="%doctrine.orm.entity_manager.class%" abstract="true" lazy="true" /> <service id="doctrine.orm.container_repository_factory" class="Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory" public="false"> <argument type="service"> <service class="Symfony\Component\DependencyInjection\ServiceLocator"> <argument type="collection" /> </service> </argument> </service> <!-- The configurator cannot be a private service --> <service id="doctrine.orm.manager_configurator.abstract" class="%doctrine.orm.manager_configurator.class%" abstract="true"> <argument type="collection" /> <argument type="collection" /> </service> <!-- validator --> <service id="doctrine.orm.validator.unique" class="%doctrine.orm.validator.unique.class%"> <tag name="validator.constraint_validator" alias="doctrine.orm.validator.unique" /> <argument type="service" id="doctrine" /> </service> <service id="doctrine.orm.validator_initializer" class="%doctrine.orm.validator_initializer.class%"> <tag name="validator.initializer" /> <argument type="service" id="doctrine" /> </service> <!-- security --> <service id="doctrine.orm.security.user.provider" class="%doctrine.orm.security.user.provider.class%" abstract="true" public="false"> <argument type="service" id="doctrine" /> </service> <!-- listeners --> <service id="doctrine.orm.listeners.resolve_target_entity" class="%doctrine.orm.listeners.resolve_target_entity.class%" public="false" /> <service id="doctrine.orm.listeners.doctrine_dbal_cache_adapter_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\DoctrineDbalCacheAdapterSchemaSubscriber"> <argument type="collection" /> <!-- DoctrineDbalAdapter instances --> <tag name="doctrine.event_subscriber" /> </service> <service id="doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber"> <argument type="collection" /> <!-- PdoAdapter instances --> <tag name="doctrine.event_subscriber" /> </service> <service id="doctrine.orm.listeners.doctrine_token_provider_schema_subscriber" class="Symfony\Bridge\Doctrine\SchemaListener\RememberMeTokenProviderDoctrineSchemaSubscriber"> <argument type="tagged" tag="security.remember_me_handler" /> <tag name="doctrine.event_subscriber" /> </service> <!-- naming strategy --> <service id="doctrine.orm.naming_strategy.default" class="%doctrine.orm.naming_strategy.default.class%" public="false" /> <service id="doctrine.orm.naming_strategy.underscore" class="%doctrine.orm.naming_strategy.underscore.class%" public="false" /> <service id="doctrine.orm.naming_strategy.underscore_number_aware" class="%doctrine.orm.naming_strategy.underscore.class%" public="false"> <argument type="constant">CASE_LOWER</argument> <argument>true</argument> </service> <!-- quote strategy --> <service id="doctrine.orm.quote_strategy.default" class="%doctrine.orm.quote_strategy.default.class%" public="false" /> <service id="doctrine.orm.quote_strategy.ansi" class="%doctrine.orm.quote_strategy.ansi.class%" public="false" /> <!-- custom id generators --> <service id="doctrine.ulid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator"> <argument type="service" id="ulid.factory" on-invalid="ignore" /> <tag name="doctrine.id_generator" /> </service> <service id="doctrine.uuid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator"> <argument type="service" id="uuid.factory" on-invalid="ignore" /> <tag name="doctrine.id_generator" /> </service> <service id="doctrine.orm.command.entity_manager_provider" class="Doctrine\Bundle\DoctrineBundle\Orm\ManagerRegistryAwareEntityManagerProvider"> <argument type="service" id="doctrine" /> </service> <service id="doctrine.orm.entity_value_resolver" class="Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver"> <argument type="service" id="doctrine" /> <argument type="service" id="doctrine.orm.entity_value_resolver.expression_language" on-invalid="ignore" /> <tag name="controller.argument_value_resolver" priority="110" /> </service> <service id="doctrine.orm.entity_value_resolver.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" /> <!-- commands --> <service id="doctrine.cache_clear_metadata_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearMetadataCacheDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-metadata" /> </service> <service id="doctrine.cache_clear_query_cache_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearQueryCacheDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-query" /> </service> <service id="doctrine.cache_clear_result_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearResultCacheDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-result" /> </service> <service id="doctrine.cache_collection_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\CollectionRegionDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-collection-region" /> </service> <service id="doctrine.mapping_convert_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ConvertMappingDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:mapping:convert" /> </service> <service id="doctrine.schema_create_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:schema:create" /> </service> <service id="doctrine.schema_drop_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\DropSchemaDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:schema:drop" /> </service> <service id="doctrine.ensure_production_settings_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\EnsureProductionSettingsDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:ensure-production-settings" /> </service> <service id="doctrine.clear_entity_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\EntityRegionCacheDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-entity-region" /> </service> <service id="doctrine.mapping_info_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\InfoDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:mapping:info" /> </service> <service id="doctrine.clear_query_region_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\QueryRegionCacheDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:cache:clear-query-region" /> </service> <service id="doctrine.query_dql_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\RunDqlDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:query:dql" /> </service> <service id="doctrine.schema_update_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:schema:update" /> </service> <service id="doctrine.schema_validate_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ValidateSchemaCommand"> <argument type="service" id="doctrine.orm.command.entity_manager_provider" /> <tag name="console.command" command="doctrine:schema:validate" /> </service> <service id="doctrine.mapping_import_command" class="Doctrine\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand"> <argument type="service" id="doctrine" /> <argument>%kernel.bundles%</argument> <tag name="console.command" command="doctrine:mapping:import" /> </service> </services> </container> doctrine-bundle/Resources/views/Collector/db.html.twig 0000644 00000052656 15120025743 0017120 0 ustar 00 {% extends request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} {% import _self as helper %} {% block toolbar %} {% if collector.querycount > 0 or collector.invalidEntityCount > 0 %} {% set icon %} {% set status = collector.invalidEntityCount > 0 ? 'red' : collector.querycount > 50 ? 'yellow' %} {{ include('@Doctrine/Collector/icon.svg') }} {% if collector.querycount == 0 and collector.invalidEntityCount > 0 %} <span class="sf-toolbar-value">{{ collector.invalidEntityCount }}</span> <span class="sf-toolbar-label">errors</span> {% else %} <span class="sf-toolbar-value">{{ collector.querycount }}</span> <span class="sf-toolbar-info-piece-additional-detail"> <span class="sf-toolbar-label">in</span> <span class="sf-toolbar-value">{{ '%0.2f'|format(collector.time * 1000) }}</span> <span class="sf-toolbar-label">ms</span> </span> {% endif %} {% endset %} {% set text %} <div class="sf-toolbar-info-piece"> <b>Database Queries</b> <span class="sf-toolbar-status {{ collector.querycount > 50 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.querycount }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Different statements</b> <span class="sf-toolbar-status">{{ collector.groupedQueryCount }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Query time</b> <span>{{ '%0.2f'|format(collector.time * 1000) }} ms</span> </div> <div class="sf-toolbar-info-piece"> <b>Invalid entities</b> <span class="sf-toolbar-status {{ collector.invalidEntityCount > 0 ? 'sf-toolbar-status-red' : '' }}">{{ collector.invalidEntityCount }}</span> </div> {% if collector.cacheEnabled %} <div class="sf-toolbar-info-piece"> <b>Cache hits</b> <span class="sf-toolbar-status sf-toolbar-status-green">{{ collector.cacheHitsCount }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Cache misses</b> <span class="sf-toolbar-status {{ collector.cacheMissesCount > 0 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.cacheMissesCount }}</span> </div> <div class="sf-toolbar-info-piece"> <b>Cache puts</b> <span class="sf-toolbar-status {{ collector.cachePutsCount > 0 ? 'sf-toolbar-status-yellow' : '' }}">{{ collector.cachePutsCount }}</span> </div> {% else %} <div class="sf-toolbar-info-piece"> <b>Second Level Cache</b> <span class="sf-toolbar-status">disabled</span> </div> {% endif %} {% endset %} {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} {% endif %} {% endblock %} {% block menu %} <span class="label {{ collector.invalidEntityCount > 0 ? 'label-status-error' }} {{ collector.querycount == 0 ? 'disabled' }}"> <span class="icon">{{ include('@Doctrine/Collector/icon.svg') }}</span> <strong>Doctrine</strong> {% if collector.invalidEntityCount %} <span class="count"> <span>{{ collector.invalidEntityCount }}</span> </span> {% endif %} </span> {% endblock %} {% block panel %} {% if 'explain' == page %} {{ render(controller('Doctrine\\Bundle\\DoctrineBundle\\Controller\\ProfilerController::explainAction', { token: token, panel: 'db', connectionName: request.query.get('connection'), query: request.query.get('query') })) }} {% else %} {{ block('queries') }} {% endif %} {% endblock %} {% block queries %} <style> .time-container { position: relative; } .time-container .nowrap { position: relative; z-index: 1; text-shadow: 0 0 2px #fff; } .time-bar { display: block; position: absolute; top: 0; left: 0; bottom: 0; background: #e0e0e0; } .sql-runnable.sf-toggle-content.sf-toggle-visible { display: flex; flex-direction: column; } .sql-runnable button { align-self: end; } </style> <h2>Query Metrics</h2> <div class="metrics"> <div class="metric"> <span class="value">{{ collector.querycount }}</span> <span class="label">Database Queries</span> </div> <div class="metric"> <span class="value">{{ collector.groupedQueryCount }}</span> <span class="label">Different statements</span> </div> <div class="metric"> <span class="value">{{ '%0.2f'|format(collector.time * 1000) }} ms</span> <span class="label">Query time</span> </div> <div class="metric"> <span class="value">{{ collector.invalidEntityCount }}</span> <span class="label">Invalid entities</span> </div> {% if collector.cacheEnabled %} <div class="metric"> <span class="value">{{ collector.cacheHitsCount }}</span> <span class="label">Cache hits</span> </div> <div class="metric"> <span class="value">{{ collector.cacheMissesCount }}</span> <span class="label">Cache misses</span> </div> <div class="metric"> <span class="value">{{ collector.cachePutsCount }}</span> <span class="label">Cache puts</span> </div> {% endif %} </div> {% set group_queries = request.query.getBoolean('group') %} {% if group_queries %} <h2>Grouped Statements</h2> <p><a href="{{ path('_profiler', { panel: 'db', token: token }) }}">Show all queries</a></p> {% else %} <h2>Queries</h2> <p><a href="{{ path('_profiler', { panel: 'db', token: token, group: true }) }}">Group similar statements</a></p> {% endif %} {% for connection, queries in collector.queries %} {% if collector.connections|length > 1 %} <h3>{{ connection }} <small>connection</small></h3> {% endif %} {% if queries is empty %} <div class="empty"> <p>No database queries were performed.</p> </div> {% else %} {% if group_queries %} {% set queries = collector.groupedQueries[connection] %} {% endif %} <table class="alt queries-table"> <thead> <tr> {% if group_queries %} <th class="nowrap" onclick="javascript:sortTable(this, 0, 'queries-{{ loop.index }}')" data-sort-direction="1" style="cursor: pointer;">Time<span class="text-muted">▼</span></th> <th class="nowrap" onclick="javascript:sortTable(this, 1, 'queries-{{ loop.index }}')" style="cursor: pointer;">Count<span></span></th> {% else %} <th class="nowrap" onclick="javascript:sortTable(this, 0, 'queries-{{ loop.index }}')" data-sort-direction="-1" style="cursor: pointer;">#<span class="text-muted">▲</span></th> <th class="nowrap" onclick="javascript:sortTable(this, 1, 'queries-{{ loop.index }}')" style="cursor: pointer;">Time<span></span></th> {% endif %} <th style="width: 100%;">Info</th> </tr> </thead> <tbody id="queries-{{ loop.index }}"> {% for i, query in queries %} {% set i = group_queries ? query.index : i %} <tr id="queryNo-{{ i }}-{{ loop.parent.loop.index }}"> {% if group_queries %} <td class="time-container"> <span class="time-bar" style="width:{{ '%0.2f'|format(query.executionPercent) }}%"></span> <span class="nowrap">{{ '%0.2f'|format(query.executionMS * 1000) }} ms<br />({{ '%0.2f'|format(query.executionPercent) }}%)</span> </td> <td class="nowrap">{{ query.count }}</td> {% else %} <td class="nowrap">{{ loop.index }}</td> <td class="nowrap">{{ '%0.2f'|format(query.executionMS * 1000) }} ms</td> {% endif %} <td> {{ query.sql|doctrine_prettify_sql }} <div> <strong class="font-normal text-small">Parameters</strong>: {{ profiler_dump(query.params, 2) }} </div> <div class="text-small font-normal"> <a href="#" class="sf-toggle link-inverse" data-toggle-selector="#formatted-query-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide formatted query">View formatted query</a> {% if query.runnable %} <a href="#" class="sf-toggle link-inverse" data-toggle-selector="#original-query-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide runnable query">View runnable query</a> {% endif %} {% if query.explainable %} <a class="link-inverse" href="{{ path('_profiler', { panel: 'db', token: token, page: 'explain', connection: connection, query: i }) }}" onclick="return explain(this);" data-target-id="explain-{{ i }}-{{ loop.parent.loop.index }}">Explain query</a> {% endif %} {% if query.backtrace is defined %} <a href="#" class="sf-toggle link-inverse" data-toggle-selector="#backtrace-{{ i }}-{{ loop.parent.loop.index }}" data-toggle-alt-content="Hide query backtrace">View query backtrace</a> {% endif %} </div> <div id="formatted-query-{{ i }}-{{ loop.parent.loop.index }}" class="sql-runnable hidden"> {{ query.sql|doctrine_format_sql(highlight = true) }} <button class="btn btn-sm hidden" data-clipboard-text="{{ query.sql|doctrine_format_sql(highlight = false)|e('html_attr') }}">Copy</button> </div> {% if query.runnable %} <div id="original-query-{{ i }}-{{ loop.parent.loop.index }}" class="sql-runnable hidden"> {% set runnable_sql = (query.sql ~ ';')|doctrine_replace_query_parameters(query.params) %} {{ runnable_sql|doctrine_prettify_sql }} <button class="btn btn-sm hidden" data-clipboard-text="{{ runnable_sql|e('html_attr') }}">Copy</button> </div> {% endif %} {% if query.explainable %} <div id="explain-{{ i }}-{{ loop.parent.loop.index }}" class="sql-explain"></div> {% endif %} {% if query.backtrace is defined %} <div id="backtrace-{{ i }}-{{ loop.parent.loop.index }}" class="hidden"> <table> <thead> <tr> <th scope="col">#</th> <th scope="col">File/Call</th> </tr> </thead> <tbody> {% for trace in query.backtrace %} <tr> <td>{{ loop.index }}</td> <td> <span class="text-small"> {% set line_number = trace.line|default(1) %} {% if trace.file is defined %} <a href="{{ trace.file|file_link(line_number) }}"> {% endif %} {{- trace.class|default ~ (trace.class is defined ? trace.type|default('::')) -}} <span class="status-warning">{{ trace.function }}</span> {% if trace.file is defined %} </a> {% endif %} (line {{ line_number }}) </span> </td> </tr> {% endfor %} </tbody> </table> </div> {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endif %} {% endfor %} <h2>Database Connections</h2> {% if not collector.connections %} <div class="empty"> <p>There are no configured database connections.</p> </div> {% else %} {{ helper.render_simple_table('Name', 'Service', collector.connections) }} {% endif %} <h2>Entity Managers</h2> {% if not collector.managers %} <div class="empty"> <p>There are no configured entity managers.</p> </div> {% else %} {{ helper.render_simple_table('Name', 'Service', collector.managers) }} {% endif %} <h2>Second Level Cache</h2> {% if not collector.cacheEnabled %} <div class="empty"> <p>Second Level Cache is not enabled.</p> </div> {% else %} {% if not collector.cacheCounts %} <div class="empty"> <p>Second level cache information is not available.</p> </div> {% else %} <div class="metrics"> <div class="metric"> <span class="value">{{ collector.cacheCounts.hits }}</span> <span class="label">Hits</span> </div> <div class="metric"> <span class="value">{{ collector.cacheCounts.misses }}</span> <span class="label">Misses</span> </div> <div class="metric"> <span class="value">{{ collector.cacheCounts.puts }}</span> <span class="label">Puts</span> </div> </div> {% if collector.cacheRegions.hits %} <h3>Number of cache hits</h3> {{ helper.render_simple_table('Region', 'Hits', collector.cacheRegions.hits) }} {% endif %} {% if collector.cacheRegions.misses %} <h3>Number of cache misses</h3> {{ helper.render_simple_table('Region', 'Misses', collector.cacheRegions.misses) }} {% endif %} {% if collector.cacheRegions.puts %} <h3>Number of cache puts</h3> {{ helper.render_simple_table('Region', 'Puts', collector.cacheRegions.puts) }} {% endif %} {% endif %} {% endif %} {% if collector.entities|length > 0 %} <h2>Entities Mapping</h2> {% for manager, classes in collector.entities %} {% if collector.managers|length > 1 %} <h3>{{ manager }} <small>entity manager</small></h3> {% endif %} {% if classes is empty %} <div class="empty"> <p>No loaded entities.</p> </div> {% else %} <table> <thead> <tr> <th scope="col">Class</th> <th scope="col">Mapping errors</th> </tr> </thead> <tbody> {% for class in classes %} {% set contains_errors = collector.mappingErrors[manager] is defined and collector.mappingErrors[manager][class] is defined %} <tr class="{{ contains_errors ? 'status-error' }}"> <td>{{ class }}</td> <td class="font-normal"> {% if contains_errors %} <ul> {% for error in collector.mappingErrors[manager][class] %} <li>{{ error }}</li> {% endfor %} </ul> {% else %} No errors. {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endif %} {% endfor %} {% endif %} <script type="text/javascript">//<![CDATA[ function explain(link) { "use strict"; var targetId = link.getAttribute('data-target-id'); var targetElement = document.getElementById(targetId); if (targetElement.style.display != 'block') { Sfjs.load(targetId, link.href, null, function(xhr, el) { el.innerHTML = 'An error occurred while loading the query explanation.'; }); targetElement.style.display = 'block'; link.innerHTML = 'Hide query explanation'; } else { targetElement.style.display = 'none'; link.innerHTML = 'Explain query'; } return false; } function sortTable(header, column, targetId) { "use strict"; var direction = parseInt(header.getAttribute('data-sort-direction')) || 1, items = [], target = document.getElementById(targetId), rows = target.children, headers = header.parentElement.children, i; for (i = 0; i < rows.length; ++i) { items.push(rows[i]); } for (i = 0; i < headers.length; ++i) { headers[i].removeAttribute('data-sort-direction'); if (headers[i].children.length > 0) { headers[i].children[0].innerHTML = ''; } } header.setAttribute('data-sort-direction', (-1*direction).toString()); header.children[0].innerHTML = direction > 0 ? '<span class="text-muted">▲</span>' : '<span class="text-muted">▼</span>'; items.sort(function(a, b) { return direction * (parseFloat(a.children[column].innerHTML) - parseFloat(b.children[column].innerHTML)); }); for (i = 0; i < items.length; ++i) { Sfjs.removeClass(items[i], i % 2 ? 'even' : 'odd'); Sfjs.addClass(items[i], i % 2 ? 'odd' : 'even'); target.appendChild(items[i]); } } if (navigator.clipboard) { document.querySelectorAll('[data-clipboard-text]').forEach(function(button) { Sfjs.removeClass(button, 'hidden'); button.addEventListener('click', function() { navigator.clipboard.writeText(button.getAttribute('data-clipboard-text')); }) }); } //]]></script> {% endblock %} {% macro render_simple_table(label1, label2, data) %} <table> <thead> <tr> <th scope="col" class="key">{{ label1 }}</th> <th scope="col">{{ label2 }}</th> </tr> </thead> <tbody> {% for key, value in data %} <tr> <th scope="row">{{ key }}</th> <td>{{ value }}</td> </tr> {% endfor %} </tbody> </table> {% endmacro %} doctrine-bundle/Resources/views/Collector/explain.html.twig 0000644 00000001565 15120025743 0020164 0 ustar 00 {% if data[0]|length > 1 %} {# The platform returns a table for the explanation (e.g. MySQL), display all columns #} <table style="margin: 5px 0;"> <thead> <tr> {% for label in data[0]|keys %} <th>{{ label }}</th> {% endfor %} </tr> </thead> <tbody> {% for row in data %} <tr> {% for key, item in row %} <td>{{ item|replace({',': ', '}) }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> {% else %} {# The Platform returns a single column for a textual explanation (e.g. PostgreSQL), display all lines #} <pre style="margin: 5px 0;"> {%- for row in data -%} {{ row|first }}{{ "\n" }} {%- endfor -%} </pre> {% endif %} doctrine-bundle/Resources/views/Collector/icon.svg 0000644 00000001223 15120025743 0016325 0 ustar 00 <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve"> <path fill="#AAAAAA" d="M5,8h14c1.7,0,3-1.3,3-3s-1.3-3-3-3H5C3.3,2,2,3.3,2,5S3.3,8,5,8z M18,3.6c0.8,0,1.5,0.7,1.5,1.5S18.8,6.6,18,6.6s-1.5-0.7-1.5-1.5S17.2,3.6,18,3.6z M19,9H5c-1.7,0-3,1.3-3,3s1.3,3,3,3h14c1.7,0,3-1.3,3-3S20.7,9,19,9z M18,13.6 c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S18.8,13.6,18,13.6z M19,16H5c-1.7,0-3,1.3-3,3s1.3,3,3,3h14c1.7,0,3-1.3,3-3S20.7,16,19,16z M18,20.6c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S18.8,20.6,18,20.6z"/> </svg> doctrine-bundle/Twig/DoctrineExtension.php 0000644 00000014111 15120025743 0014706 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle\Twig; use Doctrine\SqlFormatter\HtmlHighlighter; use Doctrine\SqlFormatter\NullHighlighter; use Doctrine\SqlFormatter\SqlFormatter; use Symfony\Component\VarDumper\Cloner\Data; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use function addslashes; use function array_key_exists; use function bin2hex; use function implode; use function is_array; use function is_bool; use function is_object; use function is_string; use function method_exists; use function preg_match; use function preg_replace_callback; use function sprintf; use function strtoupper; use function substr; use function trigger_deprecation; /** * This class contains the needed functions in order to do the query highlighting */ class DoctrineExtension extends AbstractExtension { /** @var SqlFormatter */ private $sqlFormatter; /** * Define our functions * * @return TwigFilter[] */ public function getFilters() { return [ new TwigFilter('doctrine_pretty_query', [$this, 'formatQuery'], ['is_safe' => ['html'], 'deprecated' => true]), new TwigFilter('doctrine_prettify_sql', [$this, 'prettifySql'], ['is_safe' => ['html']]), new TwigFilter('doctrine_format_sql', [$this, 'formatSql'], ['is_safe' => ['html']]), new TwigFilter('doctrine_replace_query_parameters', [$this, 'replaceQueryParameters']), ]; } /** * Escape parameters of a SQL query * DON'T USE THIS FUNCTION OUTSIDE ITS INTENDED SCOPE * * @internal * * @param mixed $parameter * * @return string */ public static function escapeFunction($parameter) { $result = $parameter; switch (true) { // Check if result is non-unicode string using PCRE_UTF8 modifier case is_string($result) && ! preg_match('//u', $result): $result = '0x' . strtoupper(bin2hex($result)); break; case is_string($result): $result = "'" . addslashes($result) . "'"; break; case is_array($result): foreach ($result as &$value) { $value = static::escapeFunction($value); } $result = implode(', ', $result) ?: 'NULL'; break; case is_object($result) && method_exists($result, '__toString'): $result = addslashes($result->__toString()); break; case $result === null: $result = 'NULL'; break; case is_bool($result): $result = $result ? '1' : '0'; break; } return $result; } /** * Return a query with the parameters replaced * * @param string $query * @param mixed[]|Data $parameters * * @return string */ public function replaceQueryParameters($query, $parameters) { if ($parameters instanceof Data) { $parameters = $parameters->getValue(true); } $i = 0; if (! array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) { $i = 1; } return preg_replace_callback( '/\?|((?<!:):[a-z0-9_]+)/i', static function ($matches) use ($parameters, &$i) { $key = substr($matches[0], 1); if (! array_key_exists($i, $parameters) && ($key === false || ! array_key_exists($key, $parameters))) { return $matches[0]; } $value = array_key_exists($i, $parameters) ? $parameters[$i] : $parameters[$key]; $result = DoctrineExtension::escapeFunction($value); $i++; return $result; }, $query ); } /** * Formats and/or highlights the given SQL statement. * * @param string $sql * @param bool $highlightOnly If true the query is not formatted, just highlighted * * @return string */ public function formatQuery($sql, $highlightOnly = false) { trigger_deprecation( 'doctrine/doctrine-bundle', '2.1', 'The "%s()" method is deprecated and will be removed in doctrine-bundle 3.0.', __METHOD__ ); $this->setUpSqlFormatter(true, true); if ($highlightOnly) { return $this->sqlFormatter->highlight($sql); } return sprintf( '<div class="highlight highlight-sql"><pre>%s</pre></div>', $this->sqlFormatter->format($sql) ); } public function prettifySql(string $sql): string { $this->setUpSqlFormatter(); return $this->sqlFormatter->highlight($sql); } public function formatSql(string $sql, bool $highlight): string { $this->setUpSqlFormatter($highlight); return $this->sqlFormatter->format($sql); } private function setUpSqlFormatter(bool $highlight = true, bool $legacy = false): void { $this->sqlFormatter = new SqlFormatter($highlight ? new HtmlHighlighter([ HtmlHighlighter::HIGHLIGHT_PRE => 'class="highlight highlight-sql"', HtmlHighlighter::HIGHLIGHT_QUOTE => 'class="string"', HtmlHighlighter::HIGHLIGHT_BACKTICK_QUOTE => 'class="string"', HtmlHighlighter::HIGHLIGHT_RESERVED => 'class="keyword"', HtmlHighlighter::HIGHLIGHT_BOUNDARY => 'class="symbol"', HtmlHighlighter::HIGHLIGHT_NUMBER => 'class="number"', HtmlHighlighter::HIGHLIGHT_WORD => 'class="word"', HtmlHighlighter::HIGHLIGHT_ERROR => 'class="error"', HtmlHighlighter::HIGHLIGHT_COMMENT => 'class="comment"', HtmlHighlighter::HIGHLIGHT_VARIABLE => 'class="variable"', ], ! $legacy) : new NullHighlighter()); } /** * Get the name of the extension * * @return string */ public function getName() { return 'doctrine_extension'; } } doctrine-bundle/.doctrine-project.json 0000644 00000004616 15120025743 0014054 0 ustar 00 { "name": "Doctrine Bundle", "shortName": "DoctrineBundle", "slug": "doctrine-bundle", "versions": [ { "name": "2.8", "branchName": "2.8.x", "slug": "latest", "upcoming": true }, { "name": "2.7", "branchName": "2.7.x", "slug": "2.7", "aliases": ["current", "stable"] }, { "name": "2.6", "branchName": "2.6.x", "slug": "2.6", "maintained": false }, { "name": "2.5", "branchName": "2.5.x", "slug": "2.5", "maintained": false }, { "name": "2.4", "branchName": "2.4.x", "slug": "2.4", "maintained": false }, { "name": "2.3", "branchName": "2.3.x", "slug": "2.3", "maintained": false }, { "name": "2.2", "branchName": "2.2.x", "slug": "2.2", "maintained": false }, { "name": "2.1", "branchName": "2.1.x", "slug": "2.1", "maintained": false }, { "name": "2.0", "branchName": "2.0.x", "slug": "2.0", "maintained": false }, { "name": "1.12", "branchName": "1.12.x", "slug": "1.12", "maintained": false }, { "name": "1.11", "branchName": "1.11.x", "slug": "1.11", "maintained": false }, { "name": "1.10", "branchName": "1.10.x", "slug": "1.10", "maintained": false }, { "name": "1.9", "branchName": "1.9.x", "slug": "1.9", "maintained": false }, { "name": "1.8", "branchName": "1.8.x", "slug": "1.8", "maintained": false }, { "name": "1.7", "branchName": "1.7.x", "slug": "1.7", "maintained": false }, { "name": "1.6", "branchName": "1.6.x", "slug": "1.6", "maintained": false }, { "name": "1.5", "branchName": "1.5.x", "slug": "1.5", "maintained": false }, { "name": "1.4", "branchName": "1.4.x", "slug": "1.4", "maintained": false }, { "name": "1.3", "branchName": "1.3.x", "slug": "1.3", "maintained": false }, { "name": "1.2", "branchName": "1.2.x", "slug": "1.2", "maintained": false }, { "name": "1.1", "branchName": "1.1.x", "slug": "1.1", "maintained": false } ] } doctrine-bundle/.symfony.bundle.yaml 0000644 00000000223 15120025743 0013534 0 ustar 00 branches: - "2.5.x" - "2.6.x" - "2.7.x" maintained_branches: - "2.6.x" - "2.7.x" doc_dir: "Resources/doc/" dev_branch: "2.7.x" doctrine-bundle/ConnectionFactory.php 0000644 00000016402 15120025743 0013764 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\Deprecations\Deprecation; use function array_merge; use function defined; use function is_subclass_of; use function trigger_deprecation; use const PHP_EOL; /** @psalm-import-type Params from DriverManager */ class ConnectionFactory { /** @var mixed[][] */ private $typesConfig = []; /** @var bool */ private $initialized = false; /** @param mixed[][] $typesConfig */ public function __construct(array $typesConfig) { $this->typesConfig = $typesConfig; } /** * Create a connection by name. * * @param mixed[] $params * @param array<string, string> $mappingTypes * @psalm-param Params $params * * @return Connection */ public function createConnection(array $params, ?Configuration $config = null, ?EventManager $eventManager = null, array $mappingTypes = []) { if (! $this->initialized) { $this->initializeTypes(); } $overriddenOptions = []; if (isset($params['connection_override_options'])) { trigger_deprecation('doctrine/doctrine-bundle', '2.4', 'The "connection_override_options" connection parameter is deprecated'); $overriddenOptions = $params['connection_override_options']; unset($params['connection_override_options']); } if (! isset($params['pdo']) && (! isset($params['charset']) || $overriddenOptions || isset($params['dbname_suffix']))) { $wrapperClass = null; if (isset($params['wrapperClass'])) { if (! is_subclass_of($params['wrapperClass'], Connection::class)) { throw DBALException::invalidWrapperClass($params['wrapperClass']); } $wrapperClass = $params['wrapperClass']; $params['wrapperClass'] = null; } $connection = DriverManager::getConnection($params, $config, $eventManager); $params = $this->addDatabaseSuffix(array_merge($connection->getParams(), $overriddenOptions)); $driver = $connection->getDriver(); $platform = $driver->getDatabasePlatform(); if (! isset($params['charset'])) { /** @psalm-suppress UndefinedClass AbstractMySQLPlatform exists since DBAL 3.x only */ if ($platform instanceof AbstractMySQLPlatform || $platform instanceof MySqlPlatform) { $params['charset'] = 'utf8mb4'; /* PARAM_ASCII_STR_ARRAY is defined since doctrine/dbal 3.3 doctrine/dbal 3.3.2 adds support for the option "collation" Checking for that constant will no longer be necessary after dropping support for doctrine/dbal 2, since this package requires doctrine/dbal 3.3.2 or higher. */ if (isset($params['defaultTableOptions']['collate']) && defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY')) { Deprecation::trigger( 'doctrine/doctrine-bundle', 'https://github.com/doctrine/dbal/issues/5214', 'The "collate" default table option is deprecated in favor of "collation" and will be removed in doctrine/doctrine-bundle 3.0. ' ); $params['defaultTableOptions']['collation'] = $params['defaultTableOptions']['collate']; unset($params['defaultTableOptions']['collate']); } $collationOption = defined('Doctrine\DBAL\Connection::PARAM_ASCII_STR_ARRAY') ? 'collation' : 'collate'; if (! isset($params['defaultTableOptions'][$collationOption])) { $params['defaultTableOptions'][$collationOption] = 'utf8mb4_unicode_ci'; } } else { $params['charset'] = 'utf8'; } } if ($wrapperClass !== null) { $params['wrapperClass'] = $wrapperClass; } else { $wrapperClass = Connection::class; } $connection = new $wrapperClass($params, $driver, $config, $eventManager); } else { $connection = DriverManager::getConnection($params, $config, $eventManager); } if (! empty($mappingTypes)) { $platform = $this->getDatabasePlatform($connection); foreach ($mappingTypes as $dbType => $doctrineType) { $platform->registerDoctrineTypeMapping($dbType, $doctrineType); } } return $connection; } /** * Try to get the database platform. * * This could fail if types should be registered to an predefined/unused connection * and the platform version is unknown. * For details have a look at DoctrineBundle issue #673. * * @throws DBALException */ private function getDatabasePlatform(Connection $connection): AbstractPlatform { try { return $connection->getDatabasePlatform(); } catch (DriverException $driverException) { throw new DBALException( 'An exception occurred while establishing a connection to figure out your platform version.' . PHP_EOL . "You can circumvent this by setting a 'server_version' configuration value" . PHP_EOL . PHP_EOL . 'For further information have a look at:' . PHP_EOL . 'https://github.com/doctrine/DoctrineBundle/issues/673', 0, $driverException ); } } /** * initialize the types */ private function initializeTypes(): void { foreach ($this->typesConfig as $typeName => $typeConfig) { if (Type::hasType($typeName)) { Type::overrideType($typeName, $typeConfig['class']); } else { Type::addType($typeName, $typeConfig['class']); } } $this->initialized = true; } /** * @param array<string, mixed> $params * * @return array<string, mixed> */ private function addDatabaseSuffix(array $params): array { if (isset($params['dbname']) && isset($params['dbname_suffix'])) { $params['dbname'] .= $params['dbname_suffix']; } foreach ($params['replica'] ?? [] as $key => $replicaParams) { if (! isset($replicaParams['dbname'], $replicaParams['dbname_suffix'])) { continue; } $params['replica'][$key]['dbname'] .= $replicaParams['dbname_suffix']; } if (isset($params['primary']['dbname'], $params['primary']['dbname_suffix'])) { $params['primary']['dbname'] .= $params['primary']['dbname_suffix']; } return $params; } } doctrine-bundle/DoctrineBundle.php 0000644 00000015335 15120025743 0013242 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheCompatibilityPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheSchemaSubscriberPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\MiddlewaresPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\RemoveLoggingMiddlewarePass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\RemoveProfilerControllerPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Driver\Middleware; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Proxy\Autoloader; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\DoctrineValidationPass; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterUidTypePass; use Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider\EntityFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension; use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; use function assert; use function class_exists; use function clearstatcache; use function interface_exists; use function spl_autoload_unregister; class DoctrineBundle extends Bundle { /** @var callable|null */ private $autoloader; /** * {@inheritDoc} */ public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass('doctrine.connections', 'doctrine.dbal.%s_connection.event_manager', 'doctrine'), PassConfig::TYPE_BEFORE_OPTIMIZATION); if ($container->hasExtension('security')) { $security = $container->getExtension('security'); if ($security instanceof SecurityExtension) { $security->addUserProviderFactory(new EntityFactory('entity', 'doctrine.orm.security.user.provider')); } } $container->addCompilerPass(new CacheCompatibilityPass()); $container->addCompilerPass(new DoctrineValidationPass('orm')); $container->addCompilerPass(new EntityListenerPass()); $container->addCompilerPass(new ServiceRepositoryCompilerPass()); $container->addCompilerPass(new IdGeneratorPass()); $container->addCompilerPass(new WellKnownSchemaFilterPass()); $container->addCompilerPass(new DbalSchemaFilterPass()); $container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10); $container->addCompilerPass(new RemoveProfilerControllerPass()); /** @psalm-suppress UndefinedClass */ if (interface_exists(Middleware::class)) { $container->addCompilerPass(new RemoveLoggingMiddlewarePass()); $container->addCompilerPass(new MiddlewaresPass()); } if (! class_exists(RegisterUidTypePass::class)) { return; } $container->addCompilerPass(new RegisterUidTypePass()); } /** * {@inheritDoc} */ public function boot() { // Register an autoloader for proxies to avoid issues when unserializing them // when the ORM is used. if (! $this->container->hasParameter('doctrine.orm.proxy_namespace')) { return; } $namespace = (string) $this->container->getParameter('doctrine.orm.proxy_namespace'); $dir = (string) $this->container->getParameter('doctrine.orm.proxy_dir'); $proxyGenerator = null; if ($this->container->getParameter('doctrine.orm.auto_generate_proxy_classes')) { // See https://github.com/symfony/symfony/pull/3419 for usage of references $container = &$this->container; $proxyGenerator = static function ($proxyDir, $proxyNamespace, $class) use (&$container): void { $originalClassName = ClassUtils::getRealClass($class); $registry = $container->get('doctrine'); assert($registry instanceof Registry); foreach ($registry->getManagers() as $em) { assert($em instanceof EntityManagerInterface); if (! $em->getConfiguration()->getAutoGenerateProxyClasses()) { continue; } $metadataFactory = $em->getMetadataFactory(); if ($metadataFactory->isTransient($originalClassName)) { continue; } $classMetadata = $metadataFactory->getMetadataFor($originalClassName); $em->getProxyFactory()->generateProxyClasses([$classMetadata]); clearstatcache(true, Autoloader::resolveFile($proxyDir, $proxyNamespace, $class)); break; } }; } $this->autoloader = Autoloader::register($dir, $namespace, $proxyGenerator); } /** * {@inheritDoc} */ public function shutdown() { if ($this->autoloader !== null) { spl_autoload_unregister($this->autoloader); $this->autoloader = null; } // Clear all entity managers to clear references to entities for GC if ($this->container->hasParameter('doctrine.entity_managers')) { foreach ($this->container->getParameter('doctrine.entity_managers') as $id) { if (! $this->container->initialized($id)) { continue; } $this->container->get($id)->clear(); } } // Close all connections to avoid reaching too many connections in the process when booting again later (tests) if (! $this->container->hasParameter('doctrine.connections')) { return; } foreach ($this->container->getParameter('doctrine.connections') as $id) { if (! $this->container->initialized($id)) { continue; } $this->container->get($id)->close(); } } /** * {@inheritDoc} */ public function registerCommands(Application $application) { } } doctrine-bundle/LICENSE 0000644 00000002066 15120025743 0010632 0 ustar 00 Copyright (c) 2011 Fabien Potencier, Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. doctrine-bundle/ManagerConfigurator.php 0000644 00000003532 15120025743 0014272 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\Filter\SQLFilter; /** * Configurator for an EntityManager */ class ManagerConfigurator { /** @var string[] */ private $enabledFilters = []; /** @var array<string,array<string,string>> */ private $filtersParameters = []; /** * @param string[] $enabledFilters * @param array<string,array<string,string>> $filtersParameters */ public function __construct(array $enabledFilters, array $filtersParameters) { $this->enabledFilters = $enabledFilters; $this->filtersParameters = $filtersParameters; } /** * Create a connection by name. */ public function configure(EntityManagerInterface $entityManager) { $this->enableFilters($entityManager); } /** * Enables filters for a given entity manager */ private function enableFilters(EntityManagerInterface $entityManager): void { if (empty($this->enabledFilters)) { return; } $filterCollection = $entityManager->getFilters(); foreach ($this->enabledFilters as $filter) { $filterObject = $filterCollection->enable($filter); if ($filterObject === null) { continue; } $this->setFilterParameters($filter, $filterObject); } } /** * Sets default parameters for a given filter */ private function setFilterParameters(string $name, SQLFilter $filter): void { if (empty($this->filtersParameters[$name])) { return; } $parameters = $this->filtersParameters[$name]; foreach ($parameters as $paramName => $paramValue) { $filter->setParameter($paramName, $paramValue); } } } doctrine-bundle/README.md 0000644 00000003052 15120025743 0011100 0 ustar 00 # Doctrine Bundle Doctrine DBAL & ORM Bundle for the Symfony Framework. [](https://github.com/doctrine/DoctrineBundle/actions/workflows/continuous-integration.yml) [](https://codecov.io/gh/doctrine/DoctrineBundle) ## What is Doctrine? The Doctrine Project is the home of a selected set of PHP libraries primarily focused on providing persistence services and related functionality. Its prize projects are a Object Relational Mapper and the Database Abstraction Layer it is built on top of. You can read more about the projects below or view a list of all projects. Object relational mapper (ORM) for PHP that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication. DBAL is a powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction. ## Documentation The documentation is rendered on [the symfony.com website](https://symfony.com/doc/current/reference/configuration/doctrine.html). The source of the documentation is available in the Resources/docs folder. doctrine-bundle/Registry.php 0000644 00000005027 15120025743 0012146 0 ustar 00 <?php namespace Doctrine\Bundle\DoctrineBundle; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\ORMException; use Doctrine\ORM\Proxy\Proxy; use ProxyManager\Proxy\LazyLoadingInterface; use Psr\Container\ContainerInterface; use Symfony\Bridge\Doctrine\ManagerRegistry; use Symfony\Component\VarExporter\LazyObjectInterface; use Symfony\Contracts\Service\ResetInterface; use function array_keys; use function assert; /** * References all Doctrine connections and entity managers in a given Container. */ class Registry extends ManagerRegistry implements ResetInterface { /** * @param string[] $connections * @param string[] $entityManagers */ public function __construct(ContainerInterface $container, array $connections, array $entityManagers, string $defaultConnection, string $defaultEntityManager) { $this->container = $container; parent::__construct('ORM', $connections, $entityManagers, $defaultConnection, $defaultEntityManager, Proxy::class); } /** * Resolves a registered namespace alias to the full namespace. * * This method looks for the alias in all registered entity managers. * * @see Configuration::getEntityNamespace * * @param string $alias The alias * * @return string The full namespace */ public function getAliasNamespace($alias) { foreach (array_keys($this->getManagers()) as $name) { $objectManager = $this->getManager($name); if (! $objectManager instanceof EntityManagerInterface) { continue; } try { return $objectManager->getConfiguration()->getEntityNamespace($alias); } catch (ORMException $e) { } } throw ORMException::unknownEntityNamespace($alias); } public function reset(): void { foreach ($this->getManagerNames() as $managerName => $serviceId) { $this->resetOrClearManager($managerName, $serviceId); } } private function resetOrClearManager(string $managerName, string $serviceId): void { if (! $this->container->initialized($serviceId)) { return; } $manager = $this->container->get($serviceId); assert($manager instanceof EntityManagerInterface); if ((! $manager instanceof LazyLoadingInterface && ! $manager instanceof LazyObjectInterface) || $manager->isOpen()) { $manager->clear(); return; } $this->resetManager($managerName); } } doctrine-bundle/UPGRADE-1.11.md 0000644 00000002107 15120025743 0011610 0 ustar 00 UPGRADE FROM 1.10 to 1.11 ========================= PHP and Symfony version support ------------------------------- * Support for PHP 5.5, 5.6 and 7.0 was dropped * Support for unsupported Symfony versions was dropped. The bundle now supports Symfony 3.4 LTS and 4.1 or newer. * Support for Twig 1.34 and below as well as 2.4 and below (for 2.x) releases was dropped. Commands -------- * Deprecated instantiating `Doctrine\Bundle\DoctrineBundle\Command` without a `ManagerRegistry` instance. * Deprecated `setContainer` and `getContainer` in `Doctrine\Bundle\DoctrineBundle\Command`. * `Doctrine\Bundle\DoctrineBundle\Command` no longer implements `ContainerAwareInterface`. Mapping ------- * Renamed `ContainerAwareEntityListenerResolver` to `ContainerEntityListenerResolver`. Types ----- * Marking types as commented in the configuration is deprecated. Instead, mark them commented using the `requiresSQLCommentHint()` method of the type. * The `commented` configuration option for types will be dropped in a future release. You should avoid using it. doctrine-bundle/UPGRADE-1.12.md 0000644 00000004701 15120025743 0011613 0 ustar 00 UPGRADE FROM 1.11 to 1.12 ========================= Deprecation of DoctrineCacheBundle ---------------------------------- With DoctrineCacheBundle [being deprecated](https://github.com/doctrine/DoctrineCacheBundle/issues/156), configuring caches through it has been deprecated. If you are using anything other than the `pool` or `id` cache types, please update your configuration to either use symfony/cache through the `pool` type or configure your cache services manually and use the `service` type. Service aliases --------------- * Deprecated the `Symfony\Bridge\Doctrine\RegistryInterface` and `Doctrine\Bundle\DoctrineBundle\Registry` service alias, use `Doctrine\Common\Persistence\ManagerRegistry` instead. * Deprecated the `Doctrine\Common\Persistence\ObjectManager` service alias, use `Doctrine\ORM\EntityManagerInterface` instead. UnitOfWork cleared between each request --------------------------------------- If all of these are true: * You call `Symfony\Bundle\FrameworkBundle\Client::disableReboot()` in your test case * Trigger multiple HTTP requests (via `Symfony\Bundle\FrameworkBundle\Client::request()` etc.) within your test case * Your test case relies on Doctrine ORM keeping references to old entities between requests (this is most obvious when calling `Doctrine\Persistence\ObjectManager::refresh`) Your test case will fail since `DoctrineBundle` 1.12.3, as identity map is now cleared between each request to better simulate real requests and avoid memory leaks. You have two options to solve this: 1. Change your test cases with new behaviour in mind. In a lot of cases this just means to replace `ObjectManager::refresh($entity)` with `$entity = ObjectManager::find($entity->getId())`. This is the recommended solution. 2. Write a compiler pass which restores old behaviour, e.g. by adding the following to your `Kernel` class: ```php protected function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { parent::build($container); if ($this->environment === 'test') { $container->addCompilerPass(new class implements \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface { public function process(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { $container->getDefinition('doctrine')->clearTag('kernel.reset'); } }, \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 1); } } ``` doctrine-bundle/UPGRADE-2.0.md 0000644 00000007372 15120025743 0011540 0 ustar 00 UPGRADE FROM 1.x to 2.0 ======================= PHP and Symfony version support ------------------------------- * Support for PHP 5.5, 5.6 and 7.0 was dropped * Support for unsupported Symfony versions was dropped. The bundle now supports Symfony 3.4 LTS and 4.1 or newer. * Support for Twig 1.34 and below as well as 2.4 and below (for 2.x) releases was dropped. * When no charset parameter is defined, it now defaults to `utf8mb4` on the MySQL platform and to `utf8` on all other platforms. Commands -------- * `Doctrine\Bundle\DoctrineBundle\Command` requires a `ManagerRegistry` instance when instantiating. * Dropped `setContainer` and `getContainer` in `Doctrine\Bundle\DoctrineBundle\Command`. * `Doctrine\Bundle\DoctrineBundle\Command` no longer implements `ContainerAwareInterface`. * `Doctrine\Bundle\DoctrineBundle\Command\GenerateEntitiesDoctrineCommand` was dropped in favour of the MakerBundle. Deprecation of DoctrineCacheBundle ---------------------------------- Configuring caches through DoctrineCacheBundle is no longer possible. Please use symfony/cache through the `pool` type or configure your cache services manually and use the `service` type. Mapping ------- * Dropped `ContainerAwareEntityListenerResolver`, use `ContainerEntityListenerResolver` instead. Registry -------- * `Registry` no longer implements `Symfony\Bridge\Doctrine\RegistryInterface`. * Removed all deprecated entity manager specific methods from the registry. Service aliases --------------- * The `Symfony\Bridge\Doctrine\RegistryInterface` interface is no longer aliased to the `doctrine` service, use `Doctrine\Common\Persistence\ManagerRegistry` instead. * The `Doctrine\Common\Persistence\ObjectManager` interface is no longer aliased to the `doctrine.orm.entity_manager` service, use `Doctrine\ORM\EntityManagerInterface` instead. Types ----- * Marking types as commented in the configuration is no longer supported. Instead, mark them commented using the `requiresSQLCommentHint()` method of the type. * The `commented` configuration option for types will be dropped in a future release. You should not use it. UnitOfWork cleared between each request --------------------------------------- If all of these are true: * You call `Symfony\Bundle\FrameworkBundle\Client::disableReboot()` in your test case * Trigger multiple HTTP requests (via `Symfony\Bundle\FrameworkBundle\Client::request()` etc.) within your test case * Your test case relies on Doctrine ORM keeping references to old entities between requests (this is most obvious when calling `Doctrine\Persistence\ObjectManager::refresh`) Your test case will fail since `DoctrineBundle` 2.0.3, as identity map is now cleared between each request to better simulate real requests and avoid memory leaks. You have two options to solve this: 1. Change your test cases with new behaviour in mind. In a lot of cases this just means to replace `ObjectManager::refresh($entity)` with `$entity = ObjectManager::find($entity->getId())`. This is the recommended solution. 2. Write a compiler pass which restores old behaviour, e.g. by adding the following to your `Kernel` class: ```php protected function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { parent::build($container); if ($this->environment === 'test') { $container->addCompilerPass(new class implements \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface { public function process(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { $container->getDefinition('doctrine')->clearTag('kernel.reset'); } }, \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 1); } } ``` doctrine-bundle/UPGRADE-2.1.md 0000644 00000004561 15120025743 0011536 0 ustar 00 UPGRADE FROM 2.0 to 2.1 ======================= Commands -------- * `Doctrine\Bundle\DoctrineBundle\Command\ImportMappingDoctrineCommand` has been deprecated Parameters ---------- Following parameters are deprecated and will no longer be defined nor consumed in future. Redefine/decorate services where they are used in via DI configuration instead. * doctrine.class * doctrine.data_collector.class * doctrine.dbal.connection.event_manager.class * doctrine.dbal.connection_factory.class * doctrine.dbal.configuration.class * doctrine.dbal.events.mysql_session_init.class * doctrine.dbal.events.oracle_session_init.class * doctrine.dbal.logger.chain.class * doctrine.dbal.logger.class * doctrine.dbal.logger.profiling.class * doctrine.orm.cache.apc.class * doctrine.orm.cache.array.class * doctrine.orm.cache.memcache.class * doctrine.orm.cache.memcache_instance.class * doctrine.orm.cache.memcached.class * doctrine.orm.cache.memcached_instance.class * doctrine.orm.cache.redis.class * doctrine.orm.cache.redis_instance.class * doctrine.orm.cache.wincache.class * doctrine.orm.cache.xcache.class * doctrine.orm.cache.zenddata.class * doctrine.orm.configuration.class * doctrine.orm.entity_listener_resolver.class * doctrine.orm.entity_manager.class * doctrine.orm.listeners.attach_entity_listeners.class * doctrine.orm.listeners.resolve_target_entity.class * doctrine.orm.manager_configurator.class * doctrine.orm.metadata.annotation.class * doctrine.orm.metadata.driver_chain.class * doctrine.orm.metadata.php.class * doctrine.orm.metadata.staticphp.class * doctrine.orm.metadata.xml.class * doctrine.orm.metadata.yml.class * doctrine.orm.naming_strategy.default.class * doctrine.orm.naming_strategy.underscore.class * doctrine.orm.proxy_cache_warmer.class * doctrine.orm.quote_strategy.ansi.class * doctrine.orm.quote_strategy.default.class * doctrine.orm.second_level_cache.cache_configuration.class * doctrine.orm.second_level_cache.default_cache_factory.class * doctrine.orm.second_level_cache.default_region.class * doctrine.orm.second_level_cache.filelock_region.class * doctrine.orm.second_level_cache.logger_chain.class * doctrine.orm.second_level_cache.logger_statistics.class * doctrine.orm.second_level_cache.regions_configuration.class * doctrine.orm.security.user.provider.class * doctrine.orm.validator.unique.class * doctrine.orm.validator_initializer.class * form.type_guesser.doctrine.class doctrine-bundle/UPGRADE-2.2.md 0000644 00000001022 15120025743 0011524 0 ustar 00 UPGRADE FROM 2.1 to 2.2 ======================= Commands -------- * `doctrine:query:sql` command has been deprecated. Use `dbal:run-sql` command instead. Configuration -------- * Following the [changes in DBAL 2.11](https://github.com/doctrine/dbal/pull/4054), we deprecated following configuration keys: * `doctrine.dbal.slaves`. Use `doctrine.dbal.replicas` * `doctrine.dbal.keep_slave`. Use `doctrine.dbal.keep_replica` Similarly, if you use XML configuration, please replace `<slave>` with `<replica>`. doctrine-bundle/UPGRADE-2.3.md 0000644 00000001406 15120025743 0011533 0 ustar 00 UPGRADE FROM 2.2 to 2.3 ======================= Commands -------- * The `\Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearMetadataCacheDoctrineCommand` (`doctrine:cache:clear-metadata`) is deprecated, metadata cache now uses PHP Array cache which can not be cleared. Configuration -------- * The `metadata_cache_driver` configuration key has been deprecated. PHP Array cache is now automatically registered when `%kernel.debug%` is false. DependencyInjection -------- * `\Doctrine\Bundle\DoctrineBundle\Dbal\BlacklistSchemaAssetFilter` has been deprecated. Implement your own include/exclude strategies. * `\Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass` has been deprecated. Implement your own include/exclude strategies. doctrine-bundle/UPGRADE-2.4.md 0000644 00000000622 15120025743 0011533 0 ustar 00 UPGRADE FROM 2.3 to 2.4 ======================= Configuration -------- * Setting the `host`, `port`, `user`, `password`, `path`, `dbname`, `unix_socket` or `memory` configuration options while the `url` one is set has been deprecated. * The `override_url` configuration option has been deprecated. ConnectionFactory -------- * The `connection_override_options` parameter has been deprecated. doctrine-bundle/UPGRADE-2.5.md 0000644 00000000223 15120025743 0011531 0 ustar 00 UPGRADE FROM 2.4 to 2.5 ======================= Configuration -------- * The `metadata_cache_driver` configuration key is no longer deprecated. doctrine-bundle/UPGRADE-2.6.md 0000644 00000000232 15120025743 0011532 0 ustar 00 UPGRADE FROM 2.5 to 2.6 ======================= Configuration ------------- * The `collate` default table option is deprecated in favor of `collation` doctrine-bundle/UPGRADE-3.0.md 0000644 00000000230 15120025743 0011523 0 ustar 00 UPGRADE FROM 2.x to 3.0 ======================= Types ----- * The `commented` configuration option for types is no longer supported and deprecated. doctrine-bundle/composer.json 0000644 00000006125 15120025743 0012347 0 ustar 00 { "name": "doctrine/doctrine-bundle", "description": "Symfony DoctrineBundle", "license": "MIT", "type": "symfony-bundle", "keywords": [ "DBAL", "ORM", "Database", "Persistence" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" }, { "name": "Doctrine Project", "homepage": "https://www.doctrine-project.org/" } ], "homepage": "https://www.doctrine-project.org", "require": { "php": "^7.1 || ^8.0", "doctrine/annotations": "^1", "doctrine/cache": "^1.11 || ^2.0", "doctrine/dbal": "^2.13.1 || ^3.3.2", "doctrine/persistence": "^2.2 || ^3", "doctrine/sql-formatter": "^1.0.1", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/config": "^4.4.3 || ^5.4 || ^6.0", "symfony/console": "^4.4 || ^5.4 || ^6.0", "symfony/dependency-injection": "^4.4.18 || ^5.4 || ^6.0", "symfony/deprecation-contracts": "^2.1 || ^3", "symfony/doctrine-bridge": "^4.4.22 || ^5.4 || ^6.0", "symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0", "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" }, "require-dev": { "doctrine/coding-standard": "^9.0", "doctrine/orm": "^2.11 || ^3.0", "friendsofphp/proxy-manager-lts": "^1.0", "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3 || ^10.0", "psalm/plugin-phpunit": "^0.16.1", "psalm/plugin-symfony": "^3", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^6.1", "symfony/property-info": "^4.4 || ^5.4 || ^6.0", "symfony/proxy-manager-bridge": "^4.4 || ^5.4 || ^6.0", "symfony/security-bundle": "^4.4 || ^5.4 || ^6.0", "symfony/twig-bridge": "^4.4 || ^5.4 || ^6.0", "symfony/validator": "^4.4 || ^5.4 || ^6.0", "symfony/web-profiler-bundle": "^4.4 || ^5.4 || ^6.0", "symfony/yaml": "^4.4 || ^5.4 || ^6.0", "twig/twig": "^1.34 || ^2.12 || ^3.0", "vimeo/psalm": "^4.7" }, "conflict": { "doctrine/orm": "<2.11 || >=3.0", "twig/twig": "<1.34 || >=2.0,<2.4" }, "suggest": { "ext-pdo": "*", "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", "symfony/web-profiler-bundle": "To use the data collector." }, "minimum-stability": "dev", "autoload": { "psr-4": { "Doctrine\\Bundle\\DoctrineBundle\\": "" } }, "autoload-dev": { "psr-4": { "": "Tests/DependencyInjection" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true, "symfony/flex": true }, "sort-packages": true } } doctrine-bundle/phpcs.xml.dist 0000644 00000002421 15120025743 0012421 0 ustar 00 <?xml version="1.0"?> <ruleset> <arg name="basepath" value="."/> <arg name="extensions" value="php"/> <arg name="parallel" value="80"/> <arg name="cache" value=".phpcs-cache"/> <arg name="colors"/> <!-- Ignore warnings, show progress of the run and show sniff names --> <arg value="nps"/> <config name="php_version" value="70100"/> <file>.</file> <exclude-pattern>vendor/*</exclude-pattern> <rule ref="Doctrine"> <exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes"/> <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/> <!-- we can do it in doctrine-bundle 3.0--> <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/> <!-- we can do it in doctrine-bundle 3.0 --> <exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix"/> <exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/> </rule> <rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses"> <exclude-pattern>Tests/*</exclude-pattern> </rule> <rule ref="Squiz.Classes.ClassFileName.NoMatch"> <exclude-pattern>Tests/*</exclude-pattern> </rule> </ruleset> doctrine-bundle/psalm.xml.dist 0000644 00000004666 15120025743 0012435 0 ustar 00 <?xml version="1.0"?> <psalm errorLevel="4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" > <plugins> <pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin"/> <pluginClass class="Psalm\PhpUnitPlugin\Plugin"/> </plugins> <projectFiles> <ignoreFiles> <directory name="vendor"/> <!-- Deprecated classes, not worth fixing --> <file name="Command/ImportMappingDoctrineCommand.php"/> <file name="DependencyInjection/Compiler/WellKnownSchemaFilterPass.php"/> </ignoreFiles> <directory name="CacheWarmer"/> <directory name="Command"/> <directory name="Controller"/> <directory name="DataCollector"/> <directory name="Dbal"/> <directory name="DependencyInjection"/> <directory name="EventSubscriber"/> <directory name="Mapping"/> <directory name="Repository"/> <directory name="Tests"/> <directory name="Twig"/> <file name="ConnectionFactory.php"/> <file name="DoctrineBundle.php"/> <file name="ManagerConfigurator.php"/> <file name="Registry.php"/> </projectFiles> <issueHandlers> <InvalidArrayOffset> <errorLevel type="suppress"> <!-- requires a release of https://github.com/doctrine/dbal/pull/5261 --> <file name="Tests/ConnectionFactoryTest.php"/> </errorLevel> </InvalidArrayOffset> <UndefinedClass> <errorLevel type="suppress"> <!-- We use the "Foo" namespace in unit tests. We are aware that those classes don't exist. --> <referencedClass name="Foo\*"/> <referencedClass name="Symfony\Bridge\Doctrine\Attribute\MapEntity"/> <referencedClass name="Symfony\Component\VarExporter\LazyObjectInterface"/> </errorLevel> </UndefinedClass> <UndefinedDocblockClass> <errorLevel type="suppress"> <!-- https://github.com/symfony/symfony/issues/45609 --> <referencedClass name="UnitEnum" /> <directory name="DependencyInjection"/> <directory name="Tests/DependencyInjection"/> </errorLevel> </UndefinedDocblockClass> </issueHandlers> </psalm> migrations/bin/doctrine-migrations 0000644 00000000202 15120025743 0013365 0 ustar 00 #!/usr/bin/env php <?php declare(strict_types=1); namespace Doctrine\Migrations; require __DIR__ . '/doctrine-migrations.php'; migrations/bin/doctrine-migrations.php 0000644 00000002100 15120025743 0014152 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Tools\Console\ConsoleRunner; use Phar; use function extension_loaded; use function file_exists; use function fwrite; use const PHP_EOL; use const STDERR; (static function (): void { $autoloadFiles = [ __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php', ]; $autoloaderFound = false; foreach ($autoloadFiles as $autoloadFile) { if (! file_exists($autoloadFile)) { continue; } require_once $autoloadFile; $autoloaderFound = true; } if (! $autoloaderFound) { if (extension_loaded('phar') && Phar::running() !== '') { fwrite(STDERR, 'The PHAR was built without dependencies!' . PHP_EOL); exit(1); } fwrite(STDERR, 'vendor/autoload.php could not be found. Did you run `composer install`?' . PHP_EOL); exit(1); } $dependencyFactory = ConsoleRunner::findDependencyFactory(); ConsoleRunner::run([], $dependencyFactory); })(); migrations/docs/en/reference/configuration.rst 0000644 00000036027 15120025743 0015620 0 ustar 00 Configuration ============= So you are ready to start configuring your migrations? We just need to provide a few bits of information for the console application in order to get started. Migrations Configuration ------------------------ First we need to configure information about your migrations. In ``/data/doctrine/migrations-docs-example`` go ahead and create a folder to store your migrations in: .. code-block:: sh $ mkdir -p lib/MyProject/Migrations Now, in the root of your project place a file named ``migrations.php``, ``migrations.yml``, ``migrations.xml`` or ``migrations.json`` and place the following contents: .. configuration-block:: .. code-block:: php <?php return [ 'table_storage' => [ 'table_name' => 'doctrine_migration_versions', 'version_column_name' => 'version', 'version_column_length' => 1024, 'executed_at_column_name' => 'executed_at', 'execution_time_column_name' => 'execution_time', ], 'migrations_paths' => [ 'MyProject\Migrations' => '/data/doctrine/migrations/lib/MyProject/Migrations', 'MyProject\Component\Migrations' => './Component/MyProject/Migrations', ], 'all_or_nothing' => true, 'check_database_platform' => true, 'organize_migrations' => 'none', 'connection' => null, 'em' => null, ]; .. code-block:: yaml table_storage: table_name: doctrine_migration_versions version_column_name: version version_column_length: 1024 executed_at_column_name: executed_at execution_time_column_name: execution_time migrations_paths: 'MyProject\Migrations': /data/doctrine/migrations/lib/MyProject/Migrations 'MyProject\Component\Migrations': ./Component/MyProject/Migrations all_or_nothing: true check_database_platform: true organize_migrations: none connection: null em: null .. code-block:: xml <?xml version="1.0" encoding="UTF-8"?> <doctrine-migrations xmlns="http://doctrine-project.org/schemas/migrations/configuration/3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/migrations/configuration/3.0 http://doctrine-project.org/schemas/migrations/configuration-3.0.xsd"> <connection>default</connection> <em>default</em> <storage> <table-storage table-name="doctrine_migration_versions" version-column-name="version" version-column-length="1024" executed-at-column-name="executed_at" execution-time-column-name="execution_time" /> </storage> <migrations-paths> <path namespace="MyProject\Migrations">/data/doctrine/migrations/lib/MyProject/Migrations</path> <path namespace="MyProject\Component\Migrations">./Component/MyProject/Migrations</path> </migrations-paths> <all-or-nothing>true</all-or-nothing> <check-database-platform>true</check-database-platform> <organize_migrations>none</organize_migrations> </doctrine-migrations> .. code-block:: json { "table_storage": { "table_name": "doctrine_migration_versions", "version_column_name": "version", "version_column_length": 1024, "executed_at_column_name": "executed_at", "execution_time_column_name": "execution_time" }, "migrations_paths": { "MyProject\\Migrations": "/data/doctrine/migrations/lib/MyProject/Migrations", "MyProject\\Component\\Migrations": "./Component/MyProject/Migrations" }, "all_or_nothing": true, "check_database_platform": true, "organize_migrations": "none", "connection": null, "em": null } Please note that if you want to use the YAML configuration option, you will need to install the ``symfony/yaml`` package with composer: .. code-block:: sh composer require symfony/yaml Here are details about what each configuration option does: +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | Name | Required | Default | Description | +============================+============+==============================+==================================================================================+ | migrations_paths<string, string> | yes | null | The PHP namespace your migration classes are located under and the path to a directory where to look for migration classes. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | table_storage | no | | Used by doctrine migrations to track the currently executed migrations | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | all_or_nothing | no | false | Whether or not to wrap multiple migrations in a single transaction. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | migrations | no | [] | Manually specify the array of migration versions instead of finding migrations. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | check_database_platform | no | true | Whether to add a database platform check at the beginning of the generated code. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | organize_migrations | no | ``none`` | Whether to organize migration classes under year (``year``) or year and month (``year_and_month``) subdirectories. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | connection | no | null | The named connection to use (available only when ConnectionRegistryConnection is used). | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | em | no | null | The named entity manager to use (available only when ManagerRegistryEntityManager is used). | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ Here the possible options for ``table_storage``: +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | Name | Required | Default | Description | +============================+============+==============================+==================================================================================+ | table_name | no | doctrine_migration_versions | The name of the table to track executed migrations in. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | version_column_name | no | version | The name of the column which stores the version name. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | version_column_length | no | 1024 | The length of the column which stores the version name. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | executed_at_column_name | no | executed_at | The name of the column which stores the date that a migration was executed. | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ | execution_time_column_name | no | execution_time | The name of the column which stores how long a migration took (milliseconds). | +----------------------------+------------+------------------------------+----------------------------------------------------------------------------------+ Manually Providing Migrations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you don't want to rely on Doctrine finding your migrations, you can explicitly specify the array of migration classes using the ``migrations`` configuration setting: .. configuration-block:: .. code-block:: php <?php return [ // .. 'migrations' => [ 'MyProject\Migrations\NewMigration', ], ]; .. code-block:: yaml // ... migrations: - "MyProject\Migrations\NewMigration" .. code-block:: xml <?xml version="1.0" encoding="UTF-8"?> <doctrine-migrations xmlns="http://doctrine-project.org/schemas/migrations/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/migrations/configuration http://doctrine-project.org/schemas/migrations/configuration.xsd"> // ... <migrations> <migration class="MyProject\Migrations\NewMigration" /> </migrations> </doctrine-migrations> .. code-block:: json { // ... "migrations": [ "DoctrineMigrations\NewMigration" ] } All or Nothing Transaction -------------------------- .. note:: This only works if your database supports transactions for DDL statements. When using the ``all_or_nothing`` option, multiple migrations ran at the same time will be wrapped in a single transaction. If one migration fails, all migrations will be rolled back From the Command Line ~~~~~~~~~~~~~~~~~~~~~ You can also set this option from the command line with the ``migrate`` command and the ``--all-or-nothing`` option: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --all-or-nothing If you have it enabled at the configuration level and want to change it for an individual migration you can pass a value of ``0`` or ``1`` to ``--all-or-nothing``. .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --all-or-nothing=0 Connection Configuration ------------------------ Now that we've configured our migrations, the next thing we need to configure is how the migrations console application knows how to get the connection to use for the migrations: Simple ~~~~~~ The simplest configuration is to put a ``migrations-db.php`` file in the root of your project and return an array of connection information that can be passed to the DBAL: .. code-block:: php <?php return [ 'dbname' => 'migrations_docs_example', 'user' => 'root', 'password' => '', 'host' => 'localhost', 'driver' => 'pdo_mysql', ]; You will need to make sure the ``migrations_docs_example`` database exists. If you are using MySQL you can create it with the following command: .. code-block:: sh $ mysqladmin create migrations_docs_example If you have already a DBAL connection available in your application, ``migrations-db.php`` can return it directly: .. code-block:: php <?php use Doctrine\DBAL\DriverManager; return DriverManager::getConnection([ 'dbname' => 'migrations_docs_example', 'user' => 'root', 'password' => '', 'host' => 'localhost', 'driver' => 'pdo_mysql', ]); Advanced ~~~~~~~~ If you require a more advanced configuration and you want to get the connection to use from your existing application setup then you can use this method of configuration. In the root of your project, place a file named ``cli-config.php`` with the following contents. It can also be placed in a folder named ``config`` if you prefer to keep it out of the root of your project. .. code-block:: php <?php require 'vendor/autoload.php'; use Doctrine\DBAL\DriverManager; use Doctrine\Migrations\Configuration\Configuration\PhpFile; use Doctrine\Migrations\Configuration\Connection\ExistingConnection; use Doctrine\Migrations\DependencyFactory; $config = new PhpFile('migrations.php'); // Or use one of the Doctrine\Migrations\Configuration\Configuration\* loaders $conn = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true]); return DependencyFactory::fromConnection($config, new ExistingConnection($conn)); The above setup assumes you are not using the ORM. If you want to use the ORM, first require it in your project with composer: .. code-block:: sh composer require doctrine/orm Now update your ``cli-config.php`` in the root of your project to look like the following: .. code-block:: php <?php require 'vendor/autoload.php'; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Setup; use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager; use Doctrine\Migrations\DependencyFactory; $config = new PhpFile('migrations.php'); // Or use one of the Doctrine\Migrations\Configuration\Configuration\* loaders $paths = [__DIR__.'/lib/MyProject/Entities']; $isDevMode = true; $ORMconfig = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); $entityManager = EntityManager::create(['driver' => 'pdo_sqlite', 'memory' => true], $ORMconfig); return DependencyFactory::fromEntityManager($config, new ExistingEntityManager($entityManager)); Make sure to create the directory where your ORM entities will be located: .. code-block:: sh $ mkdir lib/MyProject/Entities :ref:`Next Chapter: Migration Classes <migration-classes>` migrations/docs/en/reference/custom-configuration.rst 0000644 00000013644 15120025743 0017130 0 ustar 00 Custom Configuration ==================== It is possible to build a custom configuration where you manually build the ``Doctrine\Migrations\Configuration\Configuration`` instance instead of using YAML, XML, etc. In order to do this, you will need to setup a :ref:`Custom Integration <custom-integration>`. Once you have your custom integration setup, you can modify it to look like the following: .. code-block:: php #!/usr/bin/env php <?php require_once __DIR__.'/vendor/autoload.php'; use Doctrine\DBAL\DriverManager; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Connection\ExistingConnection; use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Application; $dbParams = [ 'dbname' => 'migrations_docs_example', 'user' => 'root', 'password' => '', 'host' => 'localhost', 'driver' => 'pdo_mysql', ]; $connection = DriverManager::getConnection($dbParams); $configuration = new Configuration($connection); $configuration->addMigrationsDirectory('MyProject\Migrations', '/data/doctrine/migrations-docs-example/lib/MyProject/Migrations'); $configuration->setAllOrNothing(true); $configuration->setCheckDatabasePlatform(false); $storageConfiguration = new TableMetadataStorageConfiguration(); $storageConfiguration->setTableName('doctrine_migration_versions'); $configuration->setMetadataStorageConfiguration($storageConfiguration); $dependencyFactory = DependencyFactory::fromConnection( new ExistingConfiguration($configuration), new ExistingConnection($connection) ); $cli = new Application('Doctrine Migrations'); $cli->setCatchExceptions(true); $cli->addCommands(array( new Command\DumpSchemaCommand($dependencyFactory), new Command\ExecuteCommand($dependencyFactory), new Command\GenerateCommand($dependencyFactory), new Command\LatestCommand($dependencyFactory), new Command\ListCommand($dependencyFactory), new Command\MigrateCommand($dependencyFactory), new Command\RollupCommand($dependencyFactory), new Command\StatusCommand($dependencyFactory), new Command\SyncMetadataCommand($dependencyFactory), new Command\VersionCommand($dependencyFactory), )); $cli->run(); :ref:`Next Chapter: Migrations Events <events>` It is possible to use multiple entity managers or connections, this is a way to configure your application: .. code-block:: php #!/usr/bin/env php <?php require_once __DIR__.'/vendor/autoload.php'; use Doctrine\DBAL\DriverManager; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Configuration\ExistingConfiguration; use Doctrine\Migrations\Configuration\Connection\ConnectionRegistryConnection; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Application; $connection1 = DriverManager::getConnection([...]); $connection2 = DriverManager::getConnection([...]); $connectionRegistry = new class( 'some_registry', ['foo' => $connection1, 'bar' => $connection2], [], // entity managers 'foo', // default connection null, // default entity manager 'Doctrine\Persistence\Proxy' // proxy class ) extends AbstractManagerRegistry { // implement abstract methods here }; $configuration = new Configuration($connection); $configuration->addMigrationsDirectory('MyProject\Migrations', 'some path'); $configurationLoader = new ExistingConfiguration($configuration); $connectionLoader = ConnectionRegistryConnection::withSimpleDefault($connectionRegistry); $dependencyFactory = DependencyFactory::fromConnection( $configurationLoader, $connectionLoader ); $cli = new Application('Doctrine Migrations'); $cli->setCatchExceptions(true); $cli->addCommands(array( new Command\MigrateCommand($dependencyFactory), // more commands here )); $cli->run(); With this configuration you can use the ``--conn`` parameter to specify a connection that will be used for running migrations. If the parameter is not passed, it will fallback to the one passed in the configuration, and if that is also not provided it will fallback to the default connection name specified when creating the connection registry. Custom migration template ------------------------- When the default generated migrations do not suit your needs, you may provide a custom migration template that will be used to generate future migrations. For example, if you don't need a ``down`` migration your template could look like this: .. code-block:: php <?php declare(strict_types=1); namespace <namespace>; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; final class <className> extends AbstractMigration { public function up(Schema $schema): void { <up> } } Placeholders (words inside ``< >``) are replaced with correct code upon generation. All possible wildcards are: =============== =============================================== Placeholder Description --------------- ----------------------------------------------- ``<namespace>`` Namespace of the class, e.g. ``App\Migrations`` ``<className>`` Classname, e.g. ``Version20210212092730`` ``<up>`` SQL for migrating up ``<down>`` SQL for migrating down =============== =============================================== The custom template needs to be configured. .. code-block:: php $configuration->setCustomTemplate(__DIR__ . '/custom_template.tpl'); migrations/docs/en/reference/custom-integration.rst 0000644 00000004073 15120025743 0016600 0 ustar 00 Custom Integration ================== If you don't want to use the ``./vendor/bin/doctrine-migrations`` script that comes with the project, you can always setup your own custom integration. In the root of your project, create a file named ``migrations`` and make it executable: .. code-block:: bash $ chmod +x migrations Now place the following code in the ``migrations`` file: .. code-block:: php #!/usr/bin/env php <?php require_once __DIR__.'/vendor/autoload.php'; use Doctrine\DBAL\DriverManager; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Configuration\Migration\PhpFile; use Doctrine\Migrations\Configuration\Connection\ExistingConnection; use Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Application; $dbParams = [ 'dbname' => 'migrations_docs_example', 'user' => 'root', 'password' => '', 'host' => 'localhost', 'driver' => 'pdo_mysql', ]; $connection = DriverManager::getConnection($dbParams); $config = new PhpFile('migrations.php'); // Or use one of the Doctrine\Migrations\Configuration\Configuration\* loaders $dependencyFactory = DependencyFactory::fromConnection($config, new ExistingConnection($connection)); $cli = new Application('Doctrine Migrations'); $cli->setCatchExceptions(true); $cli->addCommands(array( new Command\DumpSchemaCommand($dependencyFactory), new Command\ExecuteCommand($dependencyFactory), new Command\GenerateCommand($dependencyFactory), new Command\LatestCommand($dependencyFactory), new Command\ListCommand($dependencyFactory), new Command\MigrateCommand($dependencyFactory), new Command\RollupCommand($dependencyFactory), new Command\StatusCommand($dependencyFactory), new Command\SyncMetadataCommand($dependencyFactory), new Command\VersionCommand($dependencyFactory), )); $cli->run(); Now you can execute the migrations console application like this: .. code-block:: bash $ ./migrations migrations/docs/en/reference/events.rst 0000644 00000005122 15120025743 0014245 0 ustar 00 Migrations Events ================= The Doctrine Migrations library emits a series of events during the migration process. - ``onMigrationsMigrating``: dispatched immediately before starting to execute versions. This does not fire if there are no versions to be executed. - ``onMigrationsVersionExecuting``: dispatched before a single version executes. - ``onMigrationsVersionExecuted``: dispatched after a single version executes. - ``onMigrationsVersionSkipped``: dispatched when a single version is skipped. - ``onMigrationsMigrated``: dispatched when all versions have been executed. All of these events are emitted via the DBAL connection's event manager. Here's an example event subscriber that listens for all possible migrations events. .. code-block:: php <?php use Doctrine\Common\EventSubscriber; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Event\MigrationsVersionEventArgs; use Doctrine\Migrations\Events; class MigrationsListener implements EventSubscriber { public function getSubscribedEvents(): array { return [ Events::onMigrationsMigrating, Events::onMigrationsMigrated, Events::onMigrationsVersionExecuting, Events::onMigrationsVersionExecuted, Events::onMigrationsVersionSkipped, ]; } public function onMigrationsMigrating(MigrationsEventArgs $args): void { // ... } public function onMigrationsMigrated(MigrationsEventArgs $args): void { // ... } public function onMigrationsVersionExecuting(MigrationsVersionEventArgs $args): void { // ... } public function onMigrationsVersionExecuted(MigrationsVersionEventArgs $args): void { // ... } public function onMigrationsVersionSkipped(MigrationsVersionEventArgs $args): void { // ... } } To add an event subscriber to a connections event manager, use the ``Connection::getEventManager()`` method and the ``EventManager::addEventSubscriber()`` method: This might go in the ``cli-config.php`` file or somewhere in a frameworks container or dependency injection configuration. .. code-block:: php <?php use Doctrine\DBAL\DriverManager; $connection = DriverManager::getConnection([ // ... ]); $connection->getEventManager()->addEventSubscriber(new MigrationsListener()); // rest of the cli set up... :ref:`Next Chapter: Version Numbers <version-numbers>` migrations/docs/en/reference/generating-migrations.rst 0000644 00000023077 15120025743 0017247 0 ustar 00 Generating Migrations ===================== Doctrine can generate blank migrations for you to modify or it can generate functional migrations for you by comparing the current state of your database schema to your mapping information. Generating Blank Migrations --------------------------- To generate a blank migration you can use the ``generate`` command: .. code-block:: sh $ ./vendor/bin/doctrine-migrations generate Diffing Using the ORM --------------------- If you are using the ORM, you can modify your mapping information and have Doctrine generate a migration for you by comparing the current state of your database schema to the mapping information that is defined by using the ORM. To test this functionality, create a new ``User`` entity located at ``lib/MyProject/Entities/User.php``. .. code-block:: php <?php namespace MyProject\Entities; /** * @Entity * @Table(name="users") */ class User { /** @Id @Column(type="integer") @GeneratedValue */ private $id; /** @Column(type="string", nullable=true) */ private $username; public function setId(int $id) { $this->id = $id; } public function getId(): ?int { return $this->id; } public function setUsername(string $username): void { $this->username = $username; } public function getUsername(): ?string { return $this->username; } } Now when you run the ``diff`` command it will generate a migration which will create the ``users`` table: .. code-block:: sh $ ./vendor/bin/doctrine-migrations diff Generated new migration class to "/data/doctrine/migrations-docs-example/lib/MyProject/Migrations/Version20180601215504.php" To run just this migration for testing purposes, you can use migrations:execute --up 'MyProject\Migrations\Version20180601215504' To revert the migration you can use migrations:execute --down 'MyProject\Migrations\Version20180601215504' Take a look at the generated migration: .. note:: Notice how the table named ``example_table`` that we created earlier in the :ref:`Managing Migrations <managing-migrations>` chapter is being dropped. This is because the table is not mapped anywhere in the Doctrine ORM and the ``diff`` command detects that and generates the SQL to drop the table. If you want to ignore some tables in your database take a look at `Ignoring Custom Tables <#ignoring-custom-tables>`_ chapter. .. code-block:: php <?php declare(strict_types=1); namespace MyProject\Migrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20180601215504 extends AbstractMigration { public function getDescription(): string { return ''; } public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); $this->addSql('DROP TABLE example_table'); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL COLLATE latin1_swedish_ci, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); $this->addSql('DROP TABLE users'); } } Now you are ready to execute your diff migration: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate My Project Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating up to MyProject\Migrations\Version20180601215504 from MyProject\Migrations\Version20180601193057 ++ migrating MyProject\Migrations\Version20180601215504 -> CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB -> DROP TABLE example_table ++ migrated (took 75.9ms, used 8M memory) ------------------------ ++ finished in 84.3ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries The SQL generated here is the exact same SQL that would be executed if you were using the ``orm:schema-tool`` command. This just allows you to capture that SQL and maybe tweak it or add to it and trigger the deployment later across multiple database servers. Diffing Without the ORM ----------------------- Internally the diff command generates a ``Doctrine\DBAL\Schema\Schema`` object from your entities metadata using an implementation of ``Doctrine\Migrations\Provider\SchemaProviderInterface``. To use the Schema representation directly, without the ORM, you must implement this interface yourself. The ``SchemaProviderInterface`` only has one method named ``createSchema``. This should return a ``Doctrine\DBAL\Schema\Schema`` instance that represents the state to which you'd like to migrate your database. .. code-block:: php <?php use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Provider\SchemaProviderInterface; final class CustomSchemaProvider implements SchemaProviderInterface { public function createSchema() { $schema = new Schema(); $table = $schema->createTable('users'); $table->addColumn('id', 'integer', [ 'autoincrement' => true, ]); $table->addColumn('username', 'string', [ 'notnull' => false, ]); $table->setPrimaryKey(array('id')); return $schema; } } The ``StubSchemaProvider`` provided with the migrations library is another option. It simply takes a schema object to its constructor and returns it from ``createSchema``. .. code-block:: php <?php use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Provider\StubSchemaProvider; $schema = new Schema(); $table = $schema->createTable('users'); $table->addColumn('id', 'integer', [ 'autoincrement' => true, ]); $table->addColumn('username', 'string', [ 'notnull' => false, ]); $table->setPrimaryKey(array('id')); $provider = new StubSchemaProvider($schema); $provider->createSchema() === $schema; // true By default the Doctrine Migrations command line tool will only add the diff command if the ORM is present. Without the ORM, you'll have to add the diff command to your console application manually, passing in your schema provider implementation to the diff command's constructor. Take a look at the :ref:`Custom Integration <custom-integration>` chapter for information on how to setup a custom console application. .. code-block:: php <?php use Doctrine\Migrations\Tools\Console\Command\DiffCommand; $schemaProvider = new CustomSchemaProvider(); /** @var Symfony\Component\Console\Application */ $cli->add(new DiffCommand($schemaProvider)); // ... $cli->run(); With the custom provider in place the ``diff`` command will compare the current database schema to the one provided by the ``SchemaProviderInterface`` implementation. If there is a mismatch, the differences will be included in the generated migration just like the ORM examples above. Formatted SQL ------------- You can optionally pass the ``--formatted`` option if you want the dumped SQL to be formatted. This option uses the ``doctrine/sql-formatter`` package so you will need to install this package for it to work: .. code-block:: sh $ composer require doctrine/sql-formatter Ignoring Custom Tables ---------------------- If you have custom tables which are not managed by Doctrine you will need to tell Doctrine to ignore these tables. Otherwise, everytime you run the ``diff`` command, Doctrine will try to drop those tables. You can configure Doctrine with a schema filter. .. code-block:: php $connection->getConfiguration()->setFilterSchemaAssetsExpression("~^(?!t_)~"); With this expression all tables prefixed with t_ will ignored by the schema tool. If you use the DoctrineBundle with Symfony you can set the ``schema_filter`` option in your configuration. You can find more information in the documentation of the DoctrineMigrationsBundle. Merging Historical Migrations ----------------------------- If you have many migrations, which were generated by successive runs of the ``diff`` command over time, and you would like to replace them with one single migration, you can delete (or archive) all your "historical" migration files and run the ``diff`` command with the ``--from-empty-schema`` option. It will generate a full migration as if your database was empty. You can then use the ``rollup`` command to synchronize the version table of your (already up-to-date) database. :ref:`Next Chapter: Custom Configuration <custom-configuration>` migrations/docs/en/reference/integrations.rst 0000644 00000001426 15120025743 0015452 0 ustar 00 Integrations ============ If you are using a framework, you can use one of the pre-existing integrations built by the community. * `Symfony <https://packagist.org/packages/doctrine/doctrine-migrations-bundle>`_ * `Zend <https://packagist.org/packages/doctrine/doctrine-orm-module>`_ * `Laravel <https://packagist.org/packages/laravel-doctrine/migrations>`_ * `Silex <https://packagist.org/packages/kurl/silex-doctrine-migrations-provider>`_ * `Silex <https://packagist.org/packages/dbtlr/silex-doctrine-migrations>`_ * `Nette <https://packagist.org/packages/nettrine/migrations>`_ Don't hesitate to make a `Pull Request <https://github.com/doctrine/migrations>`_ if you want to add your integration to this list. :ref:`Next Chapter: Custom Integration <custom-integration>` migrations/docs/en/reference/introduction.rst 0000644 00000003237 15120025743 0015467 0 ustar 00 Introduction ============ The Doctrine Migrations project offers additional functionality on top of the DBAL_ and ORM_ for versioning your database schema. It makes it easy and safe to deploy changes to it in a way that can be reviewed and tested before being deployed to production. Installation ------------ You can use the Doctrine Migrations project by installing it with Composer_ or by downloading the latest PHAR from the releases_ page on GitHub. For this documentation exercise we will assume you are starting a new project so create a new folder to work in: .. code-block:: sh $ mkdir /data/doctrine/migrations-docs-example $ cd /data/doctrine/migrations-docs-example Composer ~~~~~~~~ Now to install with Composer it is as simple as running the following command in your project. .. code-block:: sh composer require "doctrine/migrations" Now you will have a file in ``vendor/bin`` available to run the migrations console application: .. code-block:: sh ./vendor/bin/doctrine-migrations PHAR ~~~~ To install by downloading the PHAR, you just need to download the latest PHAR file from the releases_ page on GitHub. Here is an example using the ``2.0.0`` release: .. code-block:: sh wget https://github.com/doctrine/migrations/releases/download/v2.0.0/doctrine-migrations.phar Now you can execute the PHAR like this: .. code-block:: sh php doctrine-migrations.phar :ref:`Next Chapter: Configuration <configuration>` .. _Composer: https://getcomposer.org/ .. _DBAL: https://www.doctrine-project.org/projects/dbal.html .. _ORM: https://www.doctrine-project.org/projects/orm.html .. _releases: https://github.com/doctrine/migrations/releases migrations/docs/en/reference/managing-migrations.rst 0000644 00000035126 15120025743 0016703 0 ustar 00 Managing Migrations =================== Managing migrations with Doctrine is easy. You can execute migrations from the console and easily revert them. You also have the option to write the SQL for a migration to a file instead of executing it from PHP. Status ------ Now that we have a new migration created, run the ``status`` command with the ``--show-versions`` option to see that the new migration is registered and ready to be executed: .. code-block:: sh $ ./vendor/bin/doctrine-migrations status --show-versions == Configuration >> Name: My Project Migrations >> Database Driver: pdo_mysql >> Database Host: localhost >> Database Name: migrations_docs_example >> Configuration Source: /data/doctrine/migrations-docs-example/migrations.php >> Version Table Name: doctrine_migration_versions >> Version Column Name: version >> Migrations Namespace: MyProject\Migrations >> Migrations Directory: /data/doctrine/migrations-docs-example/lib/MyProject/Migrations >> Previous Version: Already at first version >> Current Version: 0 >> Next Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Latest Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Executed Migrations: 0 >> Executed Unavailable Migrations: 0 >> Available Migrations: 1 >> New Migrations: 1 == Available Migration Versions >> 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) not migrated This is my example migration. As you can see we have a new migration version available and it is ready to be executed. The problem is, it does not have anything in it so nothing would be executed! Let's add some code to it and add a new table: .. code-block:: php <?php declare(strict_types=1); namespace MyProject\Migrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20180601193057 extends AbstractMigration { public function getDescription(): string { return 'This is my example migration.'; } public function up(Schema $schema): void { $this->addSql('CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); } public function down(Schema $schema): void { $this->addSql('DROP TABLE example_table'); } } Dry Run ------- Now we are ready to give it a test! First lets just do a dry-run to make sure it produces the SQL we expect: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --dry-run My Project Migrations Executing dry run of migration up to MyProject\Migrations\Version20180601193057 from 0 ++ migrating MyProject\Migrations\Version20180601193057 -> CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) ++ migrated (took 60.9ms, used 8M memory) ------------------------ ++ finished in 69.4ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries Executing Multiple Migrations ----------------------------- Everything looks good so we can remove the ``--dry-run`` option and actually execute the migration. .. note:: The ``migrate`` command will execute multiple migrations if there are multiple new unexecuted migration versions available. It will attempt to go from the current version to the latest version available. .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate My Project Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating up to MyProject\Migrations\Version20180601193057 from 0 ++ migrating MyProject\Migrations\Version20180601193057 -> CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) ++ migrated (took 47.7ms, used 8M memory) ------------------------ ++ finished in 49.1ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries Executing Single Migrations --------------------------- You may want to just execute a single migration up or down. You can do this with the ``execute`` command: .. code-block:: sh $ ./vendor/bin/doctrine-migrations execute MyProject\Migrations\Version20180601193057 --down WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n)y ++ migrating MyProject\Migrations\Version20180601193057 -> DROP TABLE example_table ++ migrated (took 42.6ms, used 8M memory) No Interaction -------------- Alternately, if you wish to run the migrations in an unattended mode, we can add the ``--no-interaction`` option and then execute the migrations without any extra prompting from Doctrine. .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --no-interaction My Project Migrations Migrating up to MyProject\Migrations\Version20180601193057 from 0 ++ migrating MyProject\Migrations\Version20180601193057 -> CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) ++ migrated (took 46.5ms, used 8M memory) ------------------------ ++ finished in 47.3ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries By checking the status again after using either method you will see everything is updated: .. code-block:: sh $ ./vendor/bin/doctrine-migrations status --show-versions == Configuration >> Name: My Project Migrations >> Database Driver: pdo_mysql >> Database Host: localhost >> Database Name: migrations_docs_example >> Configuration Source: /data/doctrine/migrations-docs-example/migrations.php >> Version Table Name: doctrine_migration_versions >> Version Column Name: version >> Migrations Namespace: MyProject\Migrations >> Migrations Directory: /data/doctrine/migrations-docs-example/lib/MyProject/Migrations >> Previous Version: 0 >> Current Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Next Version: Already at latest version >> Latest Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Executed Migrations: 1 >> Executed Unavailable Migrations: 0 >> Available Migrations: 1 >> New Migrations: 0 == Available Migration Versions >> 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) migrated (executed at 2018-06-01 17:08:44) This is my example migration. Reverting Migrations -------------------- The ``migrate`` command optionally accepts a version or version alias to migrate to. By default it will try to migrate up from the current version to the latest version. If you pass a version that is older than the current version, it will migrate down. To rollback to the the first version you can use the ``first`` version alias: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate first My Project Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating down to 0 from MyProject\Migrations\Version20180601193057 -- reverting MyProject\Migrations\Version20180601193057 -> DROP TABLE example_table -- reverted (took 38.4ms, used 8M memory) ------------------------ ++ finished in 39.5ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries Now if you run the ``status`` command again, you will see that the database is back to the way it was before: .. code-block:: sh $ ./vendor/bin/doctrine-migrations status --show-versions == Configuration >> Name: My Project Migrations >> Database Driver: pdo_mysql >> Database Host: localhost >> Database Name: migrations_docs_example >> Configuration Source: /data/doctrine/migrations-docs-example/migrations.php >> Version Table Name: doctrine_migration_versions >> Version Column Name: version >> Migrations Namespace: MyProject\Migrations >> Migrations Directory: /data/doctrine/migrations-docs-example/lib/MyProject/Migrations >> Previous Version: Already at first version >> Current Version: 0 >> Next Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Latest Version: 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) >> Executed Migrations: 0 >> Executed Unavailable Migrations: 0 >> Available Migrations: 1 >> New Migrations: 1 == Available Migration Versions >> 2018-06-01 19:30:57 (MyProject\Migrations\Version20180601193057) not migrated This is my example migration. Version Aliases --------------- You can use version aliases when executing migrations. This is for your convenience so you don't have to always know the version number. The following aliases are available: - ``first`` - Migrate down to before the first version. - ``prev`` - Migrate down to before the previous version. - ``next`` - Migrate up to the next version. - ``latest`` - Migrate up to the latest version. Here is an example where we migrate to the latest version and then revert back to the first: .. code-block:: bash $ ./vendor/bin/doctrine-migrations migrate latest $ ./vendor/bin/doctrine-migrations migrate first Writing Migration SQL Files --------------------------- You can optionally choose to not execute a migration directly on a database from PHP and instead output all the SQL statement to a file. This is possible by using the ``--write-sql`` option: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --write-sql My Project Migrations Executing dry run of migration up to MyProject\Migrations\Version20180601193057 from 0 ++ migrating MyProject\Migrations\Version20180601193057 -> CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) ++ migrated (took 55ms, used 8M memory) ------------------------ ++ finished in 60.7ms ++ used 8M memory ++ 1 migrations executed ++ 1 sql queries -- Migrating from 0 to MyProject\Migrations\Version20180601193057 Writing migration file to "/data/doctrine/migrations-docs-example/doctrine_migration_20180601172528.sql" Now if you have a look at the ``doctrine_migration_20180601172528.sql`` file you will see the would be executed SQL outputted in a nice format: .. code-block:: sh $ cat doctrine_migration_20180601172528.sql -- Doctrine Migration File Generated on 2018-06-01 17:25:28 -- Version MyProject\Migrations\Version20180601193057 CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)); INSERT INTO doctrine_migration_versions (version, executed_at) VALUES ('MyProject\Migrations\Version20180601193057', CURRENT_TIMESTAMP); The ``--write-sql`` option also accepts an optional value for where to write the sql file. It can be a relative path to a file that will write to the current working directory: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --write-sql=migration.sql Or it can be an absolute path to the file: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --write-sql=/path/to/migration.sql Or it can be a directory and it will write the default filename to it: .. code-block:: sh $ ./vendor/bin/doctrine-migrations migrate --write-sql=/path/to/directory Managing the Version Table -------------------------- Sometimes you may need to manually mark a migration as migrated or not. You can use the ``version`` command for this. .. caution:: Use caution when using the ``version`` command. If you delete a version from the table and then run the ``migrate`` command, that migration version will be executed again. .. code-block:: sh $ ./vendor/bin/doctrine-migrations version 'MyProject\Migrations\Version20180601193057' --add Or you can delete that version: .. code-block:: sh $ ./vendor/bin/doctrine-migrations version 'MyProject\Migrations\Version20180601193057' --delete This command does not actually execute any migrations, it just adds or deletes the version from the version table where we track whether or not a migration version has been executed or not. :ref:`Next Chapter: Generating Migrations <generating-migrations>` migrations/docs/en/reference/migration-classes.rst 0000644 00000013127 15120025743 0016371 0 ustar 00 Migration Classes ================= Migration classes must extend ``Doctrine\Migrations\AbstractMigration`` and at a minimum they must implement the ``up`` and ``down`` methods. You can easily generate a blank migration to modify with the following command: .. code-block:: sh $ ./vendor/bin/doctrine-migrations generate Generated new migration class to "/data/doctrine/migrations-docs-example/lib/MyProject/Migrations/Version20180601193057.php" To run just this migration for testing purposes, you can use migrations:execute --up 'MyProject\Migrations\Version20180601193057' To revert the migration you can use migrations:execute --down 'MyProject\Migrations\Version20180601193057' The above command will generate a PHP class with the path to it visible like above. Here is what the blank migration looks like: .. code-block:: php <?php declare(strict_types=1); namespace MyProject\Migrations; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class Version20180601193057 extends AbstractMigration { public function getDescription(): string { return ''; } public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs } } Methods to Implement -------------------- The ``AbstractMigration`` class provides a few methods you can override to define additional behavior for the migration. isTransactional ~~~~~~~~~~~~~~~ Override this method if you want to disable transactions in a migration. It defaults to true. .. code-block:: php public function isTransactional(): bool { return false; } .. note:: Some database platforms like MySQL or Oracle do not support DDL statements in transactions and may or may not implicitly commit the transaction opened by this library as soon as they encounter such a statement, and before running it. Make sure to read the manual of your database platform to know what is actually happening. ``isTransactional()`` does not guarantee that statements are wrapped in a single transaction. getDescription ~~~~~~~~~~~~~~ Override this method if you want to provide a description for your migration. The value returned here will get outputted when you run the ``./vendor/bin/doctrine-migrations status --show-versions`` command. .. code-block:: php public function getDescription(): string { return 'The description of my awesome migration!'; } preUp ~~~~~ This method gets called before the ``up()`` is called. .. code-block:: php public function preUp(Schema $schema): void { } postUp ~~~~~ This method gets called after the ``up()`` is called. .. code-block:: php public function postUp(Schema $schema): void { } preDown ~~~~~~~ This method gets called before the ``down()`` is called. .. code-block:: php public function preDown(Schema $schema): void { } postDown ~~~~~~~~ This method gets called after the ``down()`` is called. .. code-block:: php public function postDown(Schema $schema): void { } Methods to Call --------------- The ``AbstractMigration`` class provides a few methods you can call in your migrations to perform various functions. warnIf ~~~~~~ Warn with a message if some condition is met. .. code-block:: php public function up(Schema $schema): void { $this->warnIf(true, 'Something might be going wrong'); // ... } abortIf ~~~~~~~ Abort the migration if some condition is met: .. code-block:: php public function up(Schema $schema): void { $this->abortIf(true, 'Something went wrong. Aborting.'); // ... } skipIf ~~~~~~ Skip the migration if some condition is met. .. code-block:: php public function up(Schema $schema): void { $this->skipIf(true, 'Skipping this migration.'); // ... } addSql ~~~~~~ You can use the ``addSql`` method within the ``up`` and ``down`` methods. Internally the ``addSql`` calls are passed to the executeQuery method in the DBAL. This means that you can use the power of prepared statements easily and that you don't need to copy paste the same query with different parameters. You can just pass those different parameters to the addSql method as parameters. .. code-block:: php public function up(Schema $schema): void { $users = [ ['name' => 'mike', 'id' => 1], ['name' => 'jwage', 'id' => 2], ['name' => 'ocramius', 'id' => 3], ]; foreach ($users as $user) { $this->addSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user); } } write ~~~~~ Write some debug information to the console. .. code-block:: php public function up(Schema $schema): void { $this->write('Doing some cool migration!'); // ... } throwIrreversibleMigrationException ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If a migration cannot be reversed, you can use this exception in the ``down`` method to indicate such. The ``throwIrreversibleMigrationException`` method accepts an optional message to output. .. code-block:: php public function down(Schema $schema): void { $this->throwIrreversibleMigrationException(); // ... } :ref:`Next Chapter: Managing Migrations <managing-migrations>` migrations/docs/en/reference/version-numbers.rst 0000644 00000004151 15120025743 0016100 0 ustar 00 Version Numbers =============== When :doc:`Generating Migrations <generating-migrations>` the newly created classes are generated with the name ``Version{date}`` with ``{date}`` having a ``YmdHis`` `format <http://php.net/manual/en/function.date.php>`_. This format is important as it allows the migrations to be correctly ordered. .. note:: Starting with version 1.5 when loading migration classes, Doctrine does a ``sort($versions, SORT_STRING)`` on version numbers. This can cause problems with custom version numbers: .. code-block:: php <?php $versions = [ 'Version1', 'Version2', // ... 'Version10', ]; sort($versions, SORT_STRING); var_dump($versions); /* array(3) { [0] => string(8) "Version1" [1] => string(9) "Version10" [2] => string(8) "Version2" } */ The custom version numbers above end up out of order which may cause damage to a database. It is **strongly recommended** that the ``Version{date}`` migration class name format is used and that the various :doc:`tools for generating migrations <generating-migrations>` are used. Should some custom migration numbers be necessary, keeping the version number the same length as the date format (14 total characters) and padding it to the left with zeros should work. .. code-block:: php <?php $versions = [ 'Version00000000000001', 'Version00000000000002', // ... 'Version00000000000010', 'Version20180107070000', // generated version ]; sort($versions, SORT_STRING); var_dump($versions); /* array(4) { [0] => string(21) "Version00000000000001" [1] => string(21) "Version00000000000002" [2] => string(21) "Version00000000000010" [3] => string(21) "Version20180107070000" } */ Please note that migrating to this new, zero-padded format may require :ref:`manual version table intervention <managing-migrations#managing-the-version-table>` if the versions have previously been applied. :ref:`Next Chapter: Integrations <integrations>` migrations/docs/en/index.rst 0000644 00000001541 15120025743 0012113 0 ustar 00 Migrations Documentation ================= The Doctrine Migrations documentation is a reference guide to everything you need to know about the migrations project. Getting Help ------------ If this documentation is not helping to answer questions you have about Doctrine Migrations don't panic. You can get help from different sources: - The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_ - Slack chat room `#migrations <https://www.doctrine-project.org/slack>`_ - Report a bug on `GitHub <https://github.com/doctrine/migrations/issues>`_. - On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-migrations>`_ Getting Started --------------- The best way to get started is with the :doc:`Introduction <introduction>` section. Use the sidebar to browse other documentation for the Doctrine PHP Migrations project. migrations/docs/en/sidebar.rst 0000644 00000000500 15120025743 0012407 0 ustar 00 .. toctree:: :depth: 3 reference/introduction reference/configuration reference/migration-classes reference/managing-migrations reference/generating-migrations reference/custom-configuration reference/events reference/version-numbers reference/integrations reference/custom-integration migrations/lib/Doctrine/Migrations/Configuration/Connection/Exception/ConnectionNotSpecified.php 0000644 00000000672 15120025743 0027326 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection\Exception; use InvalidArgumentException; final class ConnectionNotSpecified extends InvalidArgumentException implements LoaderException { public static function new(): self { return new self( 'You have to specify a --db-configuration file or pass a Database Connection as a dependency to the Migrations.' ); } } migrations/lib/Doctrine/Migrations/Configuration/Connection/Exception/FileNotFound.php 0000644 00000000617 15120025743 0025265 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection\Exception; use InvalidArgumentException; use function sprintf; final class FileNotFound extends InvalidArgumentException implements LoaderException { public static function new(string $file): self { return new self(sprintf('Database configuration file "%s" does not exist.', $file)); } } migrations/lib/Doctrine/Migrations/Configuration/Connection/Exception/InvalidConfiguration.php 0000644 00000001315 15120025743 0027043 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection\Exception; use Doctrine\DBAL\Connection; use InvalidArgumentException; use function get_class; use function sprintf; final class InvalidConfiguration extends InvalidArgumentException implements LoaderException { public static function invalidArrayConfiguration(): self { return new self('The connection file has to return an array with database configuration parameters.'); } public static function invalidConnectionType(object $connection): self { return new self(sprintf('The returned connection must be a %s instance, %s returned.', Connection::class, get_class($connection))); } } migrations/lib/Doctrine/Migrations/Configuration/Connection/Exception/LoaderException.php 0000644 00000000324 15120025743 0026011 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection\Exception; use Doctrine\Migrations\Exception\MigrationException; interface LoaderException extends MigrationException { } migrations/lib/Doctrine/Migrations/Configuration/Connection/ConfigurationFile.php 0000644 00000002623 15120025743 0024401 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\Migrations\Configuration\Connection\Exception\FileNotFound; use Doctrine\Migrations\Configuration\Connection\Exception\InvalidConfiguration; use InvalidArgumentException; use function file_exists; use function is_array; /** * This class will return a Connection instance, loaded from a configuration file provided as argument. */ final class ConfigurationFile implements ConnectionLoader { /** @var string */ private $filename; public function __construct(string $filename) { $this->filename = $filename; } public function getConnection(?string $name = null): Connection { if ($name !== null) { throw new InvalidArgumentException('Only one connection is supported'); } if (! file_exists($this->filename)) { throw FileNotFound::new($this->filename); } $params = include $this->filename; if ($params instanceof Connection) { return $params; } if ($params instanceof ConnectionLoader) { return $params->getConnection(); } if (is_array($params)) { return DriverManager::getConnection($params); } throw InvalidConfiguration::invalidArrayConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Connection/ConnectionLoader.php 0000644 00000001123 15120025743 0024212 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Configuration\Connection\Exception\ConnectionNotSpecified; /** * The ConnectionLoader defines the interface used to load the Doctrine\DBAL\Connection instance to use * for migrations. */ interface ConnectionLoader { /** * Read the input and return a Connection, returns null if the config * is not supported. * * @throws ConnectionNotSpecified */ public function getConnection(?string $name = null): Connection; } migrations/lib/Doctrine/Migrations/Configuration/Connection/ConnectionRegistryConnection.php 0000644 00000002161 15120025743 0026637 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Configuration\Connection\Exception\InvalidConfiguration; use Doctrine\Persistence\ConnectionRegistry; final class ConnectionRegistryConnection implements ConnectionLoader { /** @var ConnectionRegistry */ private $registry; /** @var string|null */ private $defaultConnectionName; public static function withSimpleDefault(ConnectionRegistry $registry, ?string $connectionName = null): self { $that = new self(); $that->registry = $registry; $that->defaultConnectionName = $connectionName; return $that; } private function __construct() { } public function getConnection(?string $name = null): Connection { $connection = $this->registry->getConnection($name ?? $this->defaultConnectionName); if (! $connection instanceof Connection) { throw InvalidConfiguration::invalidConnectionType($connection); } return $connection; } } migrations/lib/Doctrine/Migrations/Configuration/Connection/ExistingConnection.php 0000644 00000001164 15120025743 0024603 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Connection; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Configuration\Exception\InvalidLoader; final class ExistingConnection implements ConnectionLoader { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function getConnection(?string $name = null): Connection { if ($name !== null) { throw InvalidLoader::noMultipleConnections($this); } return $this->connection; } } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/Exception/FileNotFound.php 0000644 00000000622 15120025743 0025731 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager\Exception; use InvalidArgumentException; use function sprintf; final class FileNotFound extends InvalidArgumentException implements LoaderException { public static function new(string $file): self { return new self(sprintf('Database configuration file "%s" does not exist.', $file)); } } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/Exception/InvalidConfiguration.php 0000644 00000001320 15120025743 0027507 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager\Exception; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use function get_class; use function sprintf; final class InvalidConfiguration extends InvalidArgumentException implements LoaderException { public static function invalidArrayConfiguration(): self { return new self('The EntityManager file has to return an array with database configuration parameters.'); } public static function invalidManagerType(object $em): self { return new self(sprintf('The returned manager must implement %s, %s returned.', EntityManagerInterface::class, get_class($em))); } } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/Exception/LoaderException.php 0000644 00000000327 15120025743 0026464 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager\Exception; use Doctrine\Migrations\Exception\MigrationException; interface LoaderException extends MigrationException { } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/ConfigurationFile.php 0000644 00000002722 15120025743 0025051 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager; use Doctrine\Migrations\Configuration\EntityManager\Exception\FileNotFound; use Doctrine\Migrations\Configuration\EntityManager\Exception\InvalidConfiguration; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use function file_exists; /** * This class will return an EntityManager instance, loaded from a configuration file provided as argument. */ final class ConfigurationFile implements EntityManagerLoader { /** @var string */ private $filename; public function __construct(string $filename) { $this->filename = $filename; } /** * Read the input and return a Configuration, returns null if the config * is not supported. * * @throws InvalidConfiguration */ public function getEntityManager(?string $name = null): EntityManagerInterface { if ($name !== null) { throw new InvalidArgumentException('Only one connection is supported'); } if (! file_exists($this->filename)) { throw FileNotFound::new($this->filename); } $params = include $this->filename; if ($params instanceof EntityManagerInterface) { return $params; } if ($params instanceof EntityManagerLoader) { return $params->getEntityManager(); } throw InvalidConfiguration::invalidArrayConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/EntityManagerLoader.php 0000644 00000000626 15120025743 0025341 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager; use Doctrine\ORM\EntityManagerInterface; /** * The EntityManagerLoader defines the interface used to load the Doctrine\DBAL\EntityManager instance to use * for migrations. * * @internal */ interface EntityManagerLoader { public function getEntityManager(?string $name = null): EntityManagerInterface; } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/ExistingEntityManager.php 0000644 00000001301 15120025743 0025714 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager; use Doctrine\Migrations\Configuration\Exception\InvalidLoader; use Doctrine\ORM\EntityManagerInterface; final class ExistingEntityManager implements EntityManagerLoader { /** @var EntityManagerInterface */ private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function getEntityManager(?string $name = null): EntityManagerInterface { if ($name !== null) { throw InvalidLoader::noMultipleEntityManagers($this); } return $this->entityManager; } } migrations/lib/Doctrine/Migrations/Configuration/EntityManager/ManagerRegistryEntityManager.php 0000644 00000002202 15120025743 0027226 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\EntityManager; use Doctrine\Migrations\Configuration\EntityManager\Exception\InvalidConfiguration; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; final class ManagerRegistryEntityManager implements EntityManagerLoader { /** @var ManagerRegistry */ private $registry; /** @var string|null */ private $defaultManagerName; public static function withSimpleDefault(ManagerRegistry $registry, ?string $managerName = null): self { $that = new self(); $that->registry = $registry; $that->defaultManagerName = $managerName; return $that; } private function __construct() { } public function getEntityManager(?string $name = null): EntityManagerInterface { $managerName = $name ?? $this->defaultManagerName; $em = $this->registry->getManager($managerName); if (! $em instanceof EntityManagerInterface) { throw InvalidConfiguration::invalidManagerType($em); } return $em; } } migrations/lib/Doctrine/Migrations/Configuration/Exception/ConfigurationException.php 0000644 00000000320 15120025743 0025307 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Exception; use Doctrine\Migrations\Exception\MigrationException; interface ConfigurationException extends MigrationException { } migrations/lib/Doctrine/Migrations/Configuration/Exception/FileNotFound.php 0000644 00000000606 15120025743 0023164 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Exception; use InvalidArgumentException; use function sprintf; final class FileNotFound extends InvalidArgumentException implements ConfigurationException { public static function new(string $file): self { return new self(sprintf('The "%s" configuration file does not exist.', $file)); } } migrations/lib/Doctrine/Migrations/Configuration/Exception/FrozenConfiguration.php 0000644 00000000524 15120025743 0024622 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Exception; use LogicException; final class FrozenConfiguration extends LogicException implements ConfigurationException { public static function new(): self { return new self('The configuration is frozen and cannot be edited anymore.'); } } migrations/lib/Doctrine/Migrations/Configuration/Exception/InvalidLoader.php 0000644 00000001431 15120025743 0023342 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Exception; use Doctrine\Migrations\Configuration\Connection\ConnectionLoader; use Doctrine\Migrations\Configuration\EntityManager\EntityManagerLoader; use InvalidArgumentException; use function get_class; use function sprintf; final class InvalidLoader extends InvalidArgumentException implements ConfigurationException { public static function noMultipleConnections(ConnectionLoader $loader): self { return new self(sprintf('Only one connection is supported by %s', get_class($loader))); } public static function noMultipleEntityManagers(EntityManagerLoader $loader): self { return new self(sprintf('Only one entity manager is supported by %s', get_class($loader))); } } migrations/lib/Doctrine/Migrations/Configuration/Exception/UnknownConfigurationValue.php 0000644 00000001076 15120025743 0026016 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Exception; use LogicException; use function sprintf; use function var_export; final class UnknownConfigurationValue extends LogicException implements ConfigurationException { /** * @param mixed $value */ public static function new(string $key, $value): self { return new self( sprintf( 'Unknown %s for configuration "%s".', var_export($value, true), $key ), 10 ); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/InvalidConfigurationFormat.php 0000644 00000000720 15120025743 0030045 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; use function sprintf; final class InvalidConfigurationFormat extends LogicException implements ConfigurationException { public static function new(string $file): self { return new self(sprintf('Configuration file "%s" cannot be parsed.', $file)); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/InvalidConfigurationKey.php 0000644 00000000727 15120025743 0027354 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; use function sprintf; final class InvalidConfigurationKey extends LogicException implements ConfigurationException { public static function new(string $key): self { return new self(sprintf('Migrations configuration key "%s" does not exist.', $key), 10); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/JsonNotValid.php 0000644 00000000612 15120025743 0025130 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; final class JsonNotValid extends LogicException implements ConfigurationException { public static function new(): self { return new self('Configuration is not valid JSON.', 10); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/MissingConfigurationFile.php 0000644 00000000647 15120025743 0027527 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; final class MissingConfigurationFile extends LogicException implements ConfigurationException { public static function new(): self { return new self('It was not possible to locate any configuration file.'); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/XmlNotValid.php 0000644 00000001043 15120025743 0024756 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; final class XmlNotValid extends LogicException implements ConfigurationException { public static function malformed(): self { return new self('The XML configuration is malformed.'); } public static function failedValidation(): self { return new self('XML configuration did not pass the validation test.', 10); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/YamlNotAvailable.php 0000644 00000001006 15120025743 0025740 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; final class YamlNotAvailable extends LogicException implements ConfigurationException { public static function new(): self { return new self( 'Unable to load yaml configuration files, please run ' . '`composer require symfony/yaml` to load yaml configuration files.' ); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/Exception/YamlNotValid.php 0000644 00000001011 15120025743 0025113 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration\Exception; use Doctrine\Migrations\Configuration\Exception\ConfigurationException; use LogicException; final class YamlNotValid extends LogicException implements ConfigurationException { public static function malformed(): self { return new self('The YAML configuration is malformed.'); } public static function invalid(): self { return new self('Configuration is not valid YAML.', 10); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/XML/configuration.xsd 0000644 00000006653 15120025743 0024151 0 ustar 00 <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://doctrine-project.org/schemas/migrations/configuration/3.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="doctrine-migrations"> <xs:complexType> <xs:all minOccurs="0"> <xs:element type="xs:string" name="name" minOccurs="0" maxOccurs="1"/> <xs:element type="xs:string" name="custom-template" minOccurs="0" maxOccurs="1"/> <xs:element name="migrations-paths" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:sequence> <xs:element name="path" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute name="namespace" type="xs:string" use="required"/> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="storage" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:sequence> <xs:any maxOccurs="1" processContents="lax"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="organize-migrations" minOccurs="0" maxOccurs="1"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="year" /> <xs:enumeration value="year_and_month" /> </xs:restriction> </xs:simpleType> </xs:element> <xs:element type="xs:string" name="connection" minOccurs="0" maxOccurs="1"/> <xs:element type="xs:string" name="em" minOccurs="0" maxOccurs="1"/> <xs:element type="xs:boolean" name="all-or-nothing" minOccurs="0" maxOccurs="1"/> <xs:element type="xs:boolean" name="check-database-platform" minOccurs="0" maxOccurs="1"/> <xs:element name="migrations" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:sequence minOccurs="0" maxOccurs="unbounded"> <xs:element name="migration" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:all> </xs:complexType> </xs:element> <xs:element name="table-storage"> <xs:complexType> <xs:attribute name="table-name" type="xs:string" use="optional"/> <xs:attribute name="version-column-name" type="xs:string" use="optional"/> <xs:attribute name="version-column-length" type="xs:positiveInteger" use="optional"/> <xs:attribute name="executed-at-column-name" type="xs:string" use="optional"/> <xs:attribute name="execution-time-column-name" type="xs:string" use="optional"/> </xs:complexType> </xs:element> </xs:schema> migrations/lib/Doctrine/Migrations/Configuration/Migration/ConfigurationArray.php 0000644 00000010644 15120025743 0024434 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Closure; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Migration\Exception\InvalidConfigurationKey; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\Migrations\Tools\BooleanStringFormatter; use function assert; use function call_user_func; use function is_array; use function is_bool; use function is_callable; final class ConfigurationArray implements ConfigurationLoader { /** @var array<string,mixed> */ private $configurations; /** * @param array<string,mixed> $configurations */ public function __construct(array $configurations) { $this->configurations = $configurations; } public function getConfiguration(): Configuration { $configMap = [ 'migrations_paths' => static function ($paths, Configuration $configuration): void { foreach ($paths as $namespace => $path) { $configuration->addMigrationsDirectory($namespace, $path); } }, 'migrations' => static function ($migrations, Configuration $configuration): void { foreach ($migrations as $className) { $configuration->addMigrationClass($className); } }, 'connection' => 'setConnectionName', 'em' => 'setEntityManagerName', 'table_storage' => [ 'table_name' => 'setTableName', 'version_column_name' => 'setVersionColumnName', 'version_column_length' => static function ($value, TableMetadataStorageConfiguration $configuration): void { $configuration->setVersionColumnLength((int) $value); }, 'executed_at_column_name' => 'setExecutedAtColumnName', 'execution_time_column_name' => 'setExecutionTimeColumnName', ], 'organize_migrations' => 'setMigrationOrganization', 'custom_template' => 'setCustomTemplate', 'all_or_nothing' => static function ($value, Configuration $configuration): void { $configuration->setAllOrNothing(is_bool($value) ? $value : BooleanStringFormatter::toBoolean($value, false)); }, 'check_database_platform' => static function ($value, Configuration $configuration): void { $configuration->setCheckDatabasePlatform(is_bool($value) ? $value : BooleanStringFormatter::toBoolean($value, false)); }, ]; $object = new Configuration(); self::applyConfigs($configMap, $object, $this->configurations); if ($object->getMetadataStorageConfiguration() === null) { $object->setMetadataStorageConfiguration(new TableMetadataStorageConfiguration()); } return $object; } /** * @param mixed[] $configMap * @param Configuration|TableMetadataStorageConfiguration $object * @param array<string|int,mixed> $data */ private static function applyConfigs(array $configMap, $object, array $data): void { foreach ($data as $configurationKey => $configurationValue) { if (! isset($configMap[$configurationKey])) { throw InvalidConfigurationKey::new((string) $configurationKey); } if (is_array($configMap[$configurationKey])) { if ($configurationKey !== 'table_storage') { throw InvalidConfigurationKey::new((string) $configurationKey); } $storageConfig = new TableMetadataStorageConfiguration(); assert($object instanceof Configuration); $object->setMetadataStorageConfiguration($storageConfig); self::applyConfigs($configMap[$configurationKey], $storageConfig, $configurationValue); } else { $callable = $configMap[$configurationKey] instanceof Closure ? $configMap[$configurationKey] : [$object, $configMap[$configurationKey]]; assert(is_callable($callable)); call_user_func( $callable, $configurationValue, $object, $data ); } } } } migrations/lib/Doctrine/Migrations/Configuration/Migration/ConfigurationFile.php 0000644 00000001376 15120025743 0024237 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use function dirname; use function realpath; abstract class ConfigurationFile implements ConfigurationLoader { /** @var string */ protected $file; public function __construct(string $file) { $this->file = $file; } /** * @param array<string,string> $directories * * @return array<string,string> */ final protected function getDirectoriesRelativeToFile(array $directories, string $file): array { foreach ($directories as $ns => $dir) { $path = realpath(dirname($file) . '/' . $dir); $directories[$ns] = $path !== false ? $path : $dir; } return $directories; } } migrations/lib/Doctrine/Migrations/Configuration/Migration/ConfigurationFileWithFallback.php 0000644 00000003360 15120025743 0026506 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Migration\Exception\MissingConfigurationFile; use Doctrine\Migrations\Tools\Console\Exception\FileTypeNotSupported; use function file_exists; /** * This class creates a configuration instance from a configuration file passed as argument. * If no arguments are provided, will try to load one of migrations.{xml, yml, yaml, json, php} files. * * @internal */ final class ConfigurationFileWithFallback implements ConfigurationLoader { /** @var string|null */ private $file; public function __construct(?string $file = null) { $this->file = $file; } public function getConfiguration(): Configuration { if ($this->file !== null) { return $this->loadConfiguration($this->file); } /** * If no config has been provided, look for default config file in the path. */ $defaultFiles = [ 'migrations.xml', 'migrations.yml', 'migrations.yaml', 'migrations.json', 'migrations.php', ]; foreach ($defaultFiles as $file) { if ($this->configurationFileExists($file)) { return $this->loadConfiguration($file); } } throw MissingConfigurationFile::new(); } private function configurationFileExists(string $config): bool { return file_exists($config); } /** * @throws FileTypeNotSupported */ private function loadConfiguration(string $file): Configuration { return (new FormattedFile($file))->getConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/ConfigurationLoader.php 0000644 00000000350 15120025743 0024555 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; interface ConfigurationLoader { public function getConfiguration(): Configuration; } migrations/lib/Doctrine/Migrations/Configuration/Migration/ExistingConfiguration.php 0000644 00000000765 15120025743 0025153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; final class ExistingConfiguration implements ConfigurationLoader { /** @var Configuration */ private $configurations; public function __construct(Configuration $configurations) { $this->configurations = $configurations; } public function getConfiguration(): Configuration { return $this->configurations; } } migrations/lib/Doctrine/Migrations/Configuration/Migration/FormattedFile.php 0000644 00000003073 15120025743 0023351 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Migration\Exception\InvalidConfigurationFormat; use function count; use function pathinfo; use const PATHINFO_EXTENSION; /** * @internal */ final class FormattedFile extends ConfigurationFile { /** @var callable[] */ private $loaders = []; private function setDefaultLoaders(): void { $this->loaders = [ 'json' => static function ($file): ConfigurationLoader { return new JsonFile($file); }, 'php' => static function ($file): ConfigurationLoader { return new PhpFile($file); }, 'xml' => static function ($file): ConfigurationLoader { return new XmlFile($file); }, 'yaml' => static function ($file): ConfigurationLoader { return new YamlFile($file); }, 'yml' => static function ($file): ConfigurationLoader { return new YamlFile($file); }, ]; } public function getConfiguration(): Configuration { if (count($this->loaders) === 0) { $this->setDefaultLoaders(); } $extension = pathinfo($this->file, PATHINFO_EXTENSION); if (! isset($this->loaders[$extension])) { throw InvalidConfigurationFormat::new($this->file); } return $this->loaders[$extension]($this->file)->getConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/JsonFile.php 0000644 00000002262 15120025743 0022334 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Exception\FileNotFound; use Doctrine\Migrations\Configuration\Migration\Exception\JsonNotValid; use function assert; use function file_exists; use function file_get_contents; use function json_decode; use function json_last_error; use const JSON_ERROR_NONE; final class JsonFile extends ConfigurationFile { public function getConfiguration(): Configuration { if (! file_exists($this->file)) { throw FileNotFound::new($this->file); } $contents = file_get_contents($this->file); assert($contents !== false); $config = json_decode($contents, true); if (json_last_error() !== JSON_ERROR_NONE) { throw JsonNotValid::new(); } if (isset($config['migrations_paths'])) { $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( $config['migrations_paths'], $this->file ); } return (new ConfigurationArray($config))->getConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/PhpFile.php 0000644 00000001672 15120025743 0022156 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Exception\FileNotFound; use function assert; use function file_exists; use function is_array; final class PhpFile extends ConfigurationFile { public function getConfiguration(): Configuration { if (! file_exists($this->file)) { throw FileNotFound::new($this->file); } $config = require $this->file; if ($config instanceof Configuration) { return $config; } assert(is_array($config)); if (isset($config['migrations_paths'])) { $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( $config['migrations_paths'], $this->file ); } return (new ConfigurationArray($config))->getConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Migration/XmlFile.php 0000644 00000007025 15120025744 0022166 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Exception\FileNotFound; use Doctrine\Migrations\Configuration\Migration\Exception\XmlNotValid; use Doctrine\Migrations\Tools\BooleanStringFormatter; use DOMDocument; use SimpleXMLElement; use function assert; use function file_exists; use function file_get_contents; use function libxml_clear_errors; use function libxml_use_internal_errors; use function simplexml_load_string; use function strtr; use const DIRECTORY_SEPARATOR; use const LIBXML_NOCDATA; final class XmlFile extends ConfigurationFile { public function getConfiguration(): Configuration { if (! file_exists($this->file)) { throw FileNotFound::new($this->file); } $this->validateXml($this->file); $rawXML = file_get_contents($this->file); assert($rawXML !== false); $root = simplexml_load_string($rawXML, SimpleXMLElement::class, LIBXML_NOCDATA); assert($root !== false); $config = $this->extractParameters($root, true); if (isset($config['all_or_nothing'])) { $config['all_or_nothing'] = BooleanStringFormatter::toBoolean( $config['all_or_nothing'], false ); } if (isset($config['migrations_paths'])) { $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( $config['migrations_paths'], $this->file ); } return (new ConfigurationArray($config))->getConfiguration(); } /** * @return mixed[] */ private function extractParameters(SimpleXMLElement $root, bool $loopOverNodes): array { $config = []; $itemsToCheck = $loopOverNodes ? $root->children() : $root->attributes(); if (! ($itemsToCheck instanceof SimpleXMLElement)) { return $config; } foreach ($itemsToCheck as $node) { $nodeName = strtr($node->getName(), '-', '_'); if ($nodeName === 'migrations_paths') { $config['migrations_paths'] = []; foreach ($node->{'path'} as $pathNode) { $config['migrations_paths'][(string) $pathNode['namespace']] = (string) $pathNode; } } elseif ($nodeName === 'storage' && $node->{'table-storage'} instanceof SimpleXMLElement) { $config['table_storage'] = $this->extractParameters($node->{'table-storage'}, false); } elseif ($nodeName === 'migrations') { $config['migrations'] = []; foreach ($node->{'migration'} as $pathNode) { $config['migrations'][] = (string) $pathNode; } } else { $config[$nodeName] = (string) $node; } } return $config; } private function validateXml(string $file): void { try { libxml_use_internal_errors(true); $xml = new DOMDocument(); if ($xml->load($file) === false) { throw XmlNotValid::malformed(); } $xsdPath = __DIR__ . DIRECTORY_SEPARATOR . 'XML' . DIRECTORY_SEPARATOR . 'configuration.xsd'; if ($xml->schemaValidate($xsdPath) === false) { throw XmlNotValid::failedValidation(); } } finally { libxml_clear_errors(); libxml_use_internal_errors(false); } } } migrations/lib/Doctrine/Migrations/Configuration/Migration/YamlFile.php 0000644 00000002753 15120025744 0022333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration\Migration; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Exception\FileNotFound; use Doctrine\Migrations\Configuration\Migration\Exception\YamlNotAvailable; use Doctrine\Migrations\Configuration\Migration\Exception\YamlNotValid; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; use function assert; use function class_exists; use function file_exists; use function file_get_contents; use function is_array; final class YamlFile extends ConfigurationFile { public function getConfiguration(): Configuration { if (! class_exists(Yaml::class)) { throw YamlNotAvailable::new(); } if (! file_exists($this->file)) { throw FileNotFound::new($this->file); } $content = file_get_contents($this->file); assert($content !== false); try { $config = Yaml::parse($content); } catch (ParseException $e) { throw YamlNotValid::malformed(); } if (! is_array($config)) { throw YamlNotValid::invalid(); } if (isset($config['migrations_paths'])) { $config['migrations_paths'] = $this->getDirectoriesRelativeToFile( $config['migrations_paths'], $this->file ); } return (new ConfigurationArray($config))->getConfiguration(); } } migrations/lib/Doctrine/Migrations/Configuration/Configuration.php 0000644 00000013544 15120025744 0021507 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Configuration; use Doctrine\Migrations\Configuration\Exception\FrozenConfiguration; use Doctrine\Migrations\Configuration\Exception\UnknownConfigurationValue; use Doctrine\Migrations\Exception\MigrationException; use Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration; use function strtolower; /** * The Configuration class is responsible for defining migration configuration information. */ final class Configuration { public const VERSIONS_ORGANIZATION_NONE = 'none'; public const VERSIONS_ORGANIZATION_BY_YEAR = 'year'; public const VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH = 'year_and_month'; /** @var array<string, string> */ private $migrationsDirectories = []; /** @var string[] */ private $migrationClasses = []; /** @var bool */ private $migrationsAreOrganizedByYear = false; /** @var bool */ private $migrationsAreOrganizedByYearAndMonth = false; /** @var string|null */ private $customTemplate; /** @var bool */ private $isDryRun = false; /** @var bool */ private $allOrNothing = false; /** @var string|null */ private $connectionName; /** @var string|null */ private $entityManagerName; /** @var bool */ private $checkDbPlatform = true; /** @var MetadataStorageConfiguration */ private $metadataStorageConfiguration; /** @var bool */ private $frozen = false; public function freeze(): void { $this->frozen = true; } private function assertNotFrozen(): void { if ($this->frozen) { throw FrozenConfiguration::new(); } } public function setMetadataStorageConfiguration(MetadataStorageConfiguration $metadataStorageConfiguration): void { $this->assertNotFrozen(); $this->metadataStorageConfiguration = $metadataStorageConfiguration; } /** * @return string[] */ public function getMigrationClasses(): array { return $this->migrationClasses; } public function addMigrationClass(string $className): void { $this->assertNotFrozen(); $this->migrationClasses[] = $className; } public function getMetadataStorageConfiguration(): ?MetadataStorageConfiguration { return $this->metadataStorageConfiguration; } public function addMigrationsDirectory(string $namespace, string $path): void { $this->assertNotFrozen(); $this->migrationsDirectories[$namespace] = $path; } /** * @return array<string,string> */ public function getMigrationDirectories(): array { return $this->migrationsDirectories; } public function getConnectionName(): ?string { return $this->connectionName; } public function setConnectionName(?string $connectionName): void { $this->assertNotFrozen(); $this->connectionName = $connectionName; } public function getEntityManagerName(): ?string { return $this->entityManagerName; } public function setEntityManagerName(?string $entityManagerName): void { $this->assertNotFrozen(); $this->entityManagerName = $entityManagerName; } public function setCustomTemplate(?string $customTemplate): void { $this->assertNotFrozen(); $this->customTemplate = $customTemplate; } public function getCustomTemplate(): ?string { return $this->customTemplate; } public function areMigrationsOrganizedByYear(): bool { return $this->migrationsAreOrganizedByYear; } /** * @throws MigrationException */ public function setMigrationsAreOrganizedByYear( bool $migrationsAreOrganizedByYear = true ): void { $this->assertNotFrozen(); $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYear; } /** * @throws MigrationException */ public function setMigrationsAreOrganizedByYearAndMonth( bool $migrationsAreOrganizedByYearAndMonth = true ): void { $this->assertNotFrozen(); $this->migrationsAreOrganizedByYear = $migrationsAreOrganizedByYearAndMonth; $this->migrationsAreOrganizedByYearAndMonth = $migrationsAreOrganizedByYearAndMonth; } public function areMigrationsOrganizedByYearAndMonth(): bool { return $this->migrationsAreOrganizedByYearAndMonth; } public function setIsDryRun(bool $isDryRun): void { $this->assertNotFrozen(); $this->isDryRun = $isDryRun; } public function isDryRun(): bool { return $this->isDryRun; } public function setAllOrNothing(bool $allOrNothing): void { $this->assertNotFrozen(); $this->allOrNothing = $allOrNothing; } public function isAllOrNothing(): bool { return $this->allOrNothing; } public function setCheckDatabasePlatform(bool $checkDbPlatform): void { $this->checkDbPlatform = $checkDbPlatform; } public function isDatabasePlatformChecked(): bool { return $this->checkDbPlatform; } public function setMigrationOrganization(string $migrationOrganization): void { $this->assertNotFrozen(); switch (strtolower($migrationOrganization)) { case self::VERSIONS_ORGANIZATION_NONE: $this->setMigrationsAreOrganizedByYearAndMonth(false); break; case self::VERSIONS_ORGANIZATION_BY_YEAR: $this->setMigrationsAreOrganizedByYear(); break; case self::VERSIONS_ORGANIZATION_BY_YEAR_AND_MONTH: $this->setMigrationsAreOrganizedByYearAndMonth(); break; default: throw UnknownConfigurationValue::new('organize_migrations', $migrationOrganization); } } } migrations/lib/Doctrine/Migrations/Event/Listeners/AutoCommitListener.php 0000644 00000001645 15120025744 0022710 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Event\Listeners; use Doctrine\Common\EventSubscriber; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Events; use Doctrine\Migrations\Tools\TransactionHelper; /** * Listens for `onMigrationsMigrated` and, if the connection has autocommit * makes sure to do the final commit to ensure changes stick around. * * @internal */ final class AutoCommitListener implements EventSubscriber { public function onMigrationsMigrated(MigrationsEventArgs $args): void { $conn = $args->getConnection(); $conf = $args->getMigratorConfiguration(); if ($conf->isDryRun() || $conn->isAutoCommit()) { return; } TransactionHelper::commitIfInTransaction($conn); } /** {@inheritdoc} */ public function getSubscribedEvents() { return [Events::onMigrationsMigrated]; } } migrations/lib/Doctrine/Migrations/Event/MigrationsEventArgs.php 0000644 00000002273 15120025744 0021102 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Metadata\MigrationPlanList; use Doctrine\Migrations\MigratorConfiguration; /** * The MigrationEventsArgs class is passed to events not related to a single migration version. */ final class MigrationsEventArgs extends EventArgs { /** @var Connection */ private $connection; /** @var MigrationPlanList */ private $plan; /** @var MigratorConfiguration */ private $migratorConfiguration; public function __construct( Connection $connection, MigrationPlanList $plan, MigratorConfiguration $migratorConfiguration ) { $this->connection = $connection; $this->plan = $plan; $this->migratorConfiguration = $migratorConfiguration; } public function getConnection(): Connection { return $this->connection; } public function getPlan(): MigrationPlanList { return $this->plan; } public function getMigratorConfiguration(): MigratorConfiguration { return $this->migratorConfiguration; } } migrations/lib/Doctrine/Migrations/Event/MigrationsVersionEventArgs.php 0000644 00000002265 15120025744 0022451 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Event; use Doctrine\Common\EventArgs; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Metadata\MigrationPlan; use Doctrine\Migrations\MigratorConfiguration; /** * The MigrationsVersionEventArgs class is passed to events related to a single migration version. */ final class MigrationsVersionEventArgs extends EventArgs { /** @var Connection */ private $connection; /** @var MigrationPlan */ private $plan; /** @var MigratorConfiguration */ private $migratorConfiguration; public function __construct( Connection $connection, MigrationPlan $plan, MigratorConfiguration $migratorConfiguration ) { $this->connection = $connection; $this->plan = $plan; $this->migratorConfiguration = $migratorConfiguration; } public function getConnection(): Connection { return $this->connection; } public function getPlan(): MigrationPlan { return $this->plan; } public function getMigratorConfiguration(): MigratorConfiguration { return $this->migratorConfiguration; } } migrations/lib/Doctrine/Migrations/Exception/AbortMigration.php 0000644 00000000266 15120025744 0020745 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class AbortMigration extends RuntimeException implements ControlException { } migrations/lib/Doctrine/Migrations/Exception/AlreadyAtVersion.php 0000644 00000000667 15120025744 0021245 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class AlreadyAtVersion extends RuntimeException implements MigrationException { public static function new(string $version): self { return new self( sprintf( 'Database is already at version %s', $version ), 6 ); } } migrations/lib/Doctrine/Migrations/Exception/ControlException.php 0000644 00000000205 15120025744 0021314 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; interface ControlException extends MigrationException { } migrations/lib/Doctrine/Migrations/Exception/DependencyException.php 0000644 00000000210 15120025744 0021746 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; interface DependencyException extends MigrationException { } migrations/lib/Doctrine/Migrations/Exception/DuplicateMigrationVersion.php 0000644 00000000773 15120025744 0023161 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class DuplicateMigrationVersion extends RuntimeException implements MigrationException { public static function new(string $version, string $class): self { return new self( sprintf( 'Migration version %s already registered with class %s', $version, $class ), 7 ); } } migrations/lib/Doctrine/Migrations/Exception/FrozenDependencies.php 0000644 00000000502 15120025744 0021567 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use LogicException; final class FrozenDependencies extends LogicException implements DependencyException { public static function new(): self { return new self('The dependencies are frozen and cannot be edited anymore.'); } } migrations/lib/Doctrine/Migrations/Exception/IrreversibleMigration.php 0000644 00000000277 15120025744 0022335 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class IrreversibleMigration extends RuntimeException implements MigrationException { } migrations/lib/Doctrine/Migrations/Exception/MetadataStorageError.php 0000644 00000001101 15120025744 0022070 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class MetadataStorageError extends RuntimeException implements MigrationException { public static function notUpToDate(): self { return new self('The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.'); } public static function notInitialized(): self { return new self('The metadata storage is not initialized, please run the sync-metadata-storage command to fix this issue.'); } } migrations/lib/Doctrine/Migrations/Exception/MigrationClassNotFound.php 0000644 00000000676 15120025744 0022425 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class MigrationClassNotFound extends RuntimeException implements MigrationException { public static function new(string $migrationClass): self { return new self( sprintf( 'Migration class "%s" was not found?', $migrationClass ) ); } } migrations/lib/Doctrine/Migrations/Exception/MigrationConfigurationConflict.php 0000644 00000001421 15120025744 0024161 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use Doctrine\Migrations\AbstractMigration; use UnexpectedValueException; use function get_class; use function sprintf; final class MigrationConfigurationConflict extends UnexpectedValueException implements MigrationException { public static function migrationIsNotTransactional(AbstractMigration $migration): self { return new self(sprintf( <<<'EXCEPTION' Context: attempting to execute migrations with all-or-nothing enabled Problem: migration %s is marked as non-transactional Solution: disable all-or-nothing in configuration or by command-line option, or enable transactions for all migrations EXCEPTION , get_class($migration) )); } } migrations/lib/Doctrine/Migrations/Exception/MigrationException.php 0000644 00000000216 15120025744 0021627 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use Throwable; interface MigrationException extends Throwable { } migrations/lib/Doctrine/Migrations/Exception/MigrationNotAvailable.php 0000644 00000000766 15120025744 0022244 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use Doctrine\Migrations\Version\Version; use RuntimeException; use function sprintf; final class MigrationNotAvailable extends RuntimeException implements MigrationException { public static function forVersion(Version $version): self { return new self( sprintf( 'The migration %s is not available', (string) $version ), 5 ); } } migrations/lib/Doctrine/Migrations/Exception/MigrationNotExecuted.php 0000644 00000000711 15120025744 0022120 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class MigrationNotExecuted extends RuntimeException implements MigrationException { public static function new(string $version): self { return new self( sprintf( 'The provided migration %s has not been executed', $version ), 5 ); } } migrations/lib/Doctrine/Migrations/Exception/MissingDependency.php 0000644 00000000677 15120025744 0021442 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class MissingDependency extends RuntimeException implements DependencyException { public static function noEntityManager(): self { return new self('The entity manager is not available.'); } public static function noSchemaProvider(): self { return new self('The schema provider is not available.'); } } migrations/lib/Doctrine/Migrations/Exception/NoMigrationsFoundWithCriteria.php 0000644 00000001050 15120025744 0023740 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class NoMigrationsFoundWithCriteria extends RuntimeException implements MigrationException { public static function new(?string $criteria = null): self { return new self( $criteria !== null ? sprintf('Could not find any migrations matching your criteria (%s).', $criteria) : 'Could not find any migrations matching your criteria.', 4 ); } } migrations/lib/Doctrine/Migrations/Exception/NoMigrationsToExecute.php 0000644 00000000636 15120025744 0022264 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use Throwable; final class NoMigrationsToExecute extends RuntimeException implements MigrationException { public static function new(?Throwable $previous = null): self { return new self( 'Could not find any migrations to execute.', 4, $previous ); } } migrations/lib/Doctrine/Migrations/Exception/NoTablesFound.php 0000644 00000000470 15120025744 0020524 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class NoTablesFound extends RuntimeException implements MigrationException { public static function new(): self { return new self('Your database schema does not contain any tables.'); } } migrations/lib/Doctrine/Migrations/Exception/PlanAlreadyExecuted.php 0000644 00000000466 15120025744 0021711 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class PlanAlreadyExecuted extends RuntimeException implements MigrationException { public static function new(): self { return new self('This plan was already marked as executed.'); } } migrations/lib/Doctrine/Migrations/Exception/RollupFailed.php 0000644 00000000633 15120025744 0020404 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class RollupFailed extends RuntimeException implements MigrationException { public static function noMigrationsFound(): self { return new self('No migrations found.'); } public static function tooManyMigrations(): self { return new self('Too many migrations.'); } } migrations/lib/Doctrine/Migrations/Exception/SkipMigration.php 0000644 00000000265 15120025744 0020603 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; final class SkipMigration extends RuntimeException implements ControlException { } migrations/lib/Doctrine/Migrations/Exception/UnknownMigrationVersion.php 0000644 00000000700 15120025744 0022674 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Exception; use RuntimeException; use function sprintf; final class UnknownMigrationVersion extends RuntimeException implements MigrationException { public static function new(string $version): self { return new self( sprintf( 'Could not find migration version %s', $version ), 5 ); } } migrations/lib/Doctrine/Migrations/Finder/Exception/FinderException.php 0000644 00000000160 15120025744 0022312 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder\Exception; interface FinderException { } migrations/lib/Doctrine/Migrations/Finder/Exception/InvalidDirectory.php 0000644 00000000637 15120025744 0022510 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder\Exception; use InvalidArgumentException; use function sprintf; final class InvalidDirectory extends InvalidArgumentException implements FinderException { public static function new(string $directory): self { return new self(sprintf('Cannot load migrations from "%s" because it is not a valid directory', $directory)); } } migrations/lib/Doctrine/Migrations/Finder/Exception/NameIsReserved.php 0000644 00000001110 15120025744 0022074 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder\Exception; use InvalidArgumentException; use function sprintf; use const PHP_EOL; final class NameIsReserved extends InvalidArgumentException implements FinderException { public static function new(string $version): self { return new self(sprintf( 'Cannot load a migrations with the name "%s" because it is reserved by Doctrine Migrations.' . PHP_EOL . 'It is used to revert all migrations including the first one.', $version )); } } migrations/lib/Doctrine/Migrations/Finder/Finder.php 0000644 00000005440 15120025744 0016503 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder; use Doctrine\Migrations\Finder\Exception\InvalidDirectory; use Doctrine\Migrations\Finder\Exception\NameIsReserved; use ReflectionClass; use function assert; use function get_declared_classes; use function in_array; use function is_dir; use function realpath; use function strlen; use function strncmp; /** * The Finder class is responsible for for finding migrations on disk at a given path. */ abstract class Finder implements MigrationFinder { protected static function requireOnce(string $path): void { require_once $path; } /** * @throws InvalidDirectory */ protected function getRealPath(string $directory): string { $dir = realpath($directory); if ($dir === false || ! is_dir($dir)) { throw InvalidDirectory::new($directory); } return $dir; } /** * @param string[] $files * * @return string[] * * @throws NameIsReserved */ protected function loadMigrations(array $files, ?string $namespace): array { $includedFiles = []; foreach ($files as $file) { static::requireOnce($file); $realFile = realpath($file); assert($realFile !== false); $includedFiles[] = $realFile; } $classes = $this->loadMigrationClasses($includedFiles, $namespace); $versions = []; foreach ($classes as $class) { $versions[] = $class->getName(); } return $versions; } /** * Look up all declared classes and find those classes contained * in the given `$files` array. * * @param string[] $files The set of files that were `required` * @param string|null $namespace If not null only classes in this namespace will be returned * * @return ReflectionClass<object>[] the classes in `$files` */ protected function loadMigrationClasses(array $files, ?string $namespace = null): array { $classes = []; foreach (get_declared_classes() as $class) { $reflectionClass = new ReflectionClass($class); if (! in_array($reflectionClass->getFileName(), $files, true)) { continue; } if ($namespace !== null && ! $this->isReflectionClassInNamespace($reflectionClass, $namespace)) { continue; } $classes[] = $reflectionClass; } return $classes; } /** * @param ReflectionClass<object> $reflectionClass */ private function isReflectionClassInNamespace(ReflectionClass $reflectionClass, string $namespace): bool { return strncmp($reflectionClass->getName(), $namespace . '\\', strlen($namespace) + 1) === 0; } } migrations/lib/Doctrine/Migrations/Finder/GlobFinder.php 0000644 00000001153 15120025744 0017304 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder; use function glob; use function rtrim; /** * The GlobFinder class finds migrations in a directory using the PHP glob() function. */ final class GlobFinder extends Finder { /** * {@inheritDoc} */ public function findMigrations(string $directory, ?string $namespace = null): array { $dir = $this->getRealPath($directory); $files = glob(rtrim($dir, '/') . '/Version*.php'); if ($files === false) { $files = []; } return $this->loadMigrations($files, $namespace); } } migrations/lib/Doctrine/Migrations/Finder/MigrationFinder.php 0000644 00000001040 15120025744 0020345 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder; /** * The MigrationFinder interface defines the interface used for finding migrations in a given directory and namespace. */ interface MigrationFinder { /** * @param string $directory The directory which the finder should search * @param string|null $namespace If not null only classes in this namespace will be returned * * @return string[] */ public function findMigrations(string $directory, ?string $namespace = null): array; } migrations/lib/Doctrine/Migrations/Finder/RecursiveRegexFinder.php 0000644 00000003342 15120025744 0021365 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Finder; use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RegexIterator; use function sprintf; use const DIRECTORY_SEPARATOR; /** * The RecursiveRegexFinder class recursively searches the given directory for migrations. */ final class RecursiveRegexFinder extends Finder { /** @var string */ private $pattern; public function __construct(?string $pattern = null) { $this->pattern = $pattern ?? sprintf( '#^.+\\%s[^\\%s]+\\.php$#i', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ); } /** * @return string[] */ public function findMigrations(string $directory, ?string $namespace = null): array { $dir = $this->getRealPath($directory); return $this->loadMigrations( $this->getMatches($this->createIterator($dir)), $namespace ); } private function createIterator(string $dir): RegexIterator { return new RegexIterator( new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS), RecursiveIteratorIterator::LEAVES_ONLY ), $this->getPattern(), RegexIterator::GET_MATCH ); } private function getPattern(): string { return $this->pattern; } /** * @return string[] */ private function getMatches(RegexIterator $iteratorFilesMatch): array { $files = []; foreach ($iteratorFilesMatch as $file) { $files[] = $file[0]; } return $files; } } migrations/lib/Doctrine/Migrations/Generator/Exception/GeneratorException.php 0000644 00000000310 15120025744 0023545 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator\Exception; use Doctrine\Migrations\Exception\MigrationException; interface GeneratorException extends MigrationException { } migrations/lib/Doctrine/Migrations/Generator/Exception/InvalidTemplateSpecified.php 0000644 00000001347 15120025744 0024651 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator\Exception; use InvalidArgumentException; use function sprintf; final class InvalidTemplateSpecified extends InvalidArgumentException implements GeneratorException { public static function notFoundOrNotReadable(string $path): self { return new self(sprintf('The specified template "%s" cannot be found or is not readable.', $path)); } public static function notReadable(string $path): self { return new self(sprintf('The specified template "%s" could not be read.', $path)); } public static function empty(string $path): self { return new self(sprintf('The specified template "%s" is empty.', $path)); } } migrations/lib/Doctrine/Migrations/Generator/Exception/NoChangesDetected.php 0000644 00000000505 15120025744 0023255 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator\Exception; use RuntimeException; final class NoChangesDetected extends RuntimeException implements GeneratorException { public static function new(): self { return new self('No changes detected in your mapping information.'); } } migrations/lib/Doctrine/Migrations/Generator/ClassNameGenerator.php 0000644 00000001034 15120025744 0021523 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use DateTimeImmutable; use DateTimeZone; /*final */class ClassNameGenerator { public const VERSION_FORMAT = 'YmdHis'; public function generateClassName(string $namespace): string { return $namespace . '\\Version' . $this->generateVersionNumber(); } private function generateVersionNumber(): string { $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); return $now->format(self::VERSION_FORMAT); } } migrations/lib/Doctrine/Migrations/Generator/ConcatenationFileBuilder.php 0000644 00000002022 15120025744 0022700 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use DateTimeImmutable; use DateTimeInterface; use Doctrine\Migrations\Query\Query; use function sprintf; /** * The ConcatenationFileBuilder class is responsible for building a migration SQL file from an array of queries per version. * * @internal */ final class ConcatenationFileBuilder implements FileBuilder { /** @param array<string,Query[]> $queriesByVersion */ public function buildMigrationFile( array $queriesByVersion, string $direction, ?DateTimeInterface $now = null ): string { $now = $now ?? new DateTimeImmutable(); $string = sprintf("-- Doctrine Migration File Generated on %s\n", $now->format('Y-m-d H:i:s')); foreach ($queriesByVersion as $version => $queries) { $string .= "\n-- Version " . $version . "\n"; foreach ($queries as $query) { $string .= $query->getStatement() . ";\n"; } } return $string; } } migrations/lib/Doctrine/Migrations/Generator/DiffGenerator.php 0000644 00000011225 15120025744 0020530 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use Doctrine\DBAL\Configuration as DBALConfiguration; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractAsset; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Generator\Exception\NoChangesDetected; use Doctrine\Migrations\Provider\SchemaProvider; use function preg_match; use function strpos; use function substr; /** * The DiffGenerator class is responsible for comparing two Doctrine\DBAL\Schema\Schema instances and generating a * migration class with the SQL statements needed to migrate from one schema to the other. * * @internal */ class DiffGenerator { /** @var DBALConfiguration */ private $dbalConfiguration; /** @var AbstractSchemaManager */ private $schemaManager; /** @var SchemaProvider */ private $schemaProvider; /** @var AbstractPlatform */ private $platform; /** @var Generator */ private $migrationGenerator; /** @var SqlGenerator */ private $migrationSqlGenerator; /** @var SchemaProvider */ private $emptySchemaProvider; public function __construct( DBALConfiguration $dbalConfiguration, AbstractSchemaManager $schemaManager, SchemaProvider $schemaProvider, AbstractPlatform $platform, Generator $migrationGenerator, SqlGenerator $migrationSqlGenerator, SchemaProvider $emptySchemaProvider ) { $this->dbalConfiguration = $dbalConfiguration; $this->schemaManager = $schemaManager; $this->schemaProvider = $schemaProvider; $this->platform = $platform; $this->migrationGenerator = $migrationGenerator; $this->migrationSqlGenerator = $migrationSqlGenerator; $this->emptySchemaProvider = $emptySchemaProvider; } /** * @throws NoChangesDetected */ public function generate( string $fqcn, ?string $filterExpression, bool $formatted = false, int $lineLength = 120, bool $checkDbPlatform = true, bool $fromEmptySchema = false ): string { if ($filterExpression !== null) { $this->dbalConfiguration->setSchemaAssetsFilter( static function ($assetName) use ($filterExpression) { if ($assetName instanceof AbstractAsset) { $assetName = $assetName->getName(); } return preg_match($filterExpression, $assetName); } ); } $fromSchema = $fromEmptySchema ? $this->createEmptySchema() : $this->createFromSchema(); $toSchema = $this->createToSchema(); $up = $this->migrationSqlGenerator->generate( $fromSchema->getMigrateToSql($toSchema, $this->platform), $formatted, $lineLength, $checkDbPlatform ); $down = $this->migrationSqlGenerator->generate( $fromSchema->getMigrateFromSql($toSchema, $this->platform), $formatted, $lineLength, $checkDbPlatform ); if ($up === '' && $down === '') { throw NoChangesDetected::new(); } return $this->migrationGenerator->generateMigration( $fqcn, $up, $down ); } private function createEmptySchema(): Schema { return $this->emptySchemaProvider->createSchema(); } private function createFromSchema(): Schema { return $this->schemaManager->createSchema(); } private function createToSchema(): Schema { $toSchema = $this->schemaProvider->createSchema(); $schemaAssetsFilter = $this->dbalConfiguration->getSchemaAssetsFilter(); if ($schemaAssetsFilter !== null) { foreach ($toSchema->getTables() as $table) { $tableName = $table->getName(); if ($schemaAssetsFilter($this->resolveTableName($tableName))) { continue; } $toSchema->dropTable($tableName); } } return $toSchema; } /** * Resolve a table name from its fully qualified name. The `$name` argument * comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return * a namespaced name with the form `{namespace}.{tableName}`. This extracts * the table name from that. */ private function resolveTableName(string $name): string { $pos = strpos($name, '.'); return $pos === false ? $name : substr($name, $pos + 1); } } migrations/lib/Doctrine/Migrations/Generator/FileBuilder.php 0000644 00000000755 15120025744 0020205 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use DateTimeInterface; use Doctrine\Migrations\Query\Query; /** * The ConcatenationFileBuilder class is responsible for building a migration SQL file from an array of queries per version. * * @internal */ interface FileBuilder { /** @param array<string,Query[]> $queriesByVersion */ public function buildMigrationFile(array $queriesByVersion, string $direction, ?DateTimeInterface $now = null): string; } migrations/lib/Doctrine/Migrations/Generator/Generator.php 0000644 00000007670 15120025744 0017750 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Generator\Exception\InvalidTemplateSpecified; use Doctrine\Migrations\Tools\Console\Helper\MigrationDirectoryHelper; use InvalidArgumentException; use function explode; use function file_get_contents; use function file_put_contents; use function implode; use function is_file; use function is_readable; use function preg_match; use function preg_replace; use function sprintf; use function strtr; use function trim; /** * The Generator class is responsible for generating a migration class. * * @internal */ class Generator { private const MIGRATION_TEMPLATE = <<<'TEMPLATE' <?php declare(strict_types=1); namespace <namespace>; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ final class <className> extends AbstractMigration { public function getDescription(): string { return ''; } public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs <up> } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs <down> } } TEMPLATE; /** @var Configuration */ private $configuration; /** @var string|null */ private $template; public function __construct(Configuration $configuration) { $this->configuration = $configuration; } public function generateMigration( string $fqcn, ?string $up = null, ?string $down = null ): string { $mch = []; if (preg_match('~(.*)\\\\([^\\\\]+)~', $fqcn, $mch) === 0) { throw new InvalidArgumentException(sprintf('Invalid FQCN')); } [$fqcn, $namespace, $className] = $mch; $dirs = $this->configuration->getMigrationDirectories(); if (! isset($dirs[$namespace])) { throw new InvalidArgumentException(sprintf('Path not defined for the namespace "%s"', $namespace)); } $dir = $dirs[$namespace]; $replacements = [ '<namespace>' => $namespace, '<className>' => $className, '<up>' => $up !== null ? ' ' . implode("\n ", explode("\n", $up)) : null, '<down>' => $down !== null ? ' ' . implode("\n ", explode("\n", $down)) : null, ]; $code = strtr($this->getTemplate(), $replacements); $code = preg_replace('/^ +$/m', '', $code); $directoryHelper = new MigrationDirectoryHelper(); $dir = $directoryHelper->getMigrationDirectory($this->configuration, $dir); $path = $dir . '/' . $className . '.php'; file_put_contents($path, $code); return $path; } private function getTemplate(): string { if ($this->template === null) { $this->template = $this->loadCustomTemplate(); if ($this->template === null) { $this->template = self::MIGRATION_TEMPLATE; } } return $this->template; } /** * @throws InvalidTemplateSpecified */ private function loadCustomTemplate(): ?string { $customTemplate = $this->configuration->getCustomTemplate(); if ($customTemplate === null) { return null; } if (! is_file($customTemplate) || ! is_readable($customTemplate)) { throw InvalidTemplateSpecified::notFoundOrNotReadable($customTemplate); } $content = file_get_contents($customTemplate); if ($content === false) { throw InvalidTemplateSpecified::notReadable($customTemplate); } if (trim($content) === '') { throw InvalidTemplateSpecified::empty($customTemplate); } return $content; } } migrations/lib/Doctrine/Migrations/Generator/SqlGenerator.php 0000644 00000005031 15120025744 0020415 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Generator; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\SqlFormatter\NullHighlighter; use Doctrine\SqlFormatter\SqlFormatter; use function array_unshift; use function count; use function implode; use function sprintf; use function stripos; use function strlen; use function var_export; /** * The SqlGenerator class is responsible for generating the body of the up() and down() methods for a migration * from an array of SQL queries. * * @internal */ class SqlGenerator { /** @var Configuration */ private $configuration; /** @var AbstractPlatform */ private $platform; public function __construct(Configuration $configuration, AbstractPlatform $platform) { $this->configuration = $configuration; $this->platform = $platform; } /** @param string[] $sql */ public function generate( array $sql, bool $formatted = false, int $lineLength = 120, bool $checkDbPlatform = true ): string { $code = []; $storageConfiguration = $this->configuration->getMetadataStorageConfiguration(); foreach ($sql as $query) { if ( $storageConfiguration instanceof TableMetadataStorageConfiguration && stripos($query, $storageConfiguration->getTableName()) !== false ) { continue; } if ($formatted) { $maxLength = $lineLength - 18 - 8; // max - php code length - indentation if (strlen($query) > $maxLength) { $query = (new SqlFormatter(new NullHighlighter()))->format($query); } } $code[] = sprintf('$this->addSql(%s);', var_export($query, true)); } if (count($code) !== 0 && $checkDbPlatform && $this->configuration->isDatabasePlatformChecked()) { $currentPlatform = $this->platform->getName(); array_unshift( $code, sprintf( '$this->abortIf($this->connection->getDatabasePlatform()->getName() !== %s, %s);', var_export($currentPlatform, true), var_export(sprintf("Migration can only be executed safely on '%s'.", $currentPlatform), true) ), '' ); } return implode("\n", $code); } } migrations/lib/Doctrine/Migrations/Metadata/Storage/MetadataStorage.php 0000644 00000000665 15120025744 0022262 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata\Storage; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Version\ExecutionResult; interface MetadataStorage { public function ensureInitialized(): void; public function getExecutedMigrations(): ExecutedMigrationsList; public function complete(ExecutionResult $migration): void; public function reset(): void; } migrations/lib/Doctrine/Migrations/Metadata/Storage/MetadataStorageConfiguration.php 0000644 00000000175 15120025744 0025006 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata\Storage; interface MetadataStorageConfiguration { } migrations/lib/Doctrine/Migrations/Metadata/Storage/TableMetadataStorage.php 0000644 00000022622 15120025744 0023227 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata\Storage; use DateTimeImmutable; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Types\Types; use Doctrine\Migrations\Exception\MetadataStorageError; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\ExecutedMigration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\MigrationsRepository; use Doctrine\Migrations\Version\Comparator as MigrationsComparator; use Doctrine\Migrations\Version\Direction; use Doctrine\Migrations\Version\ExecutionResult; use Doctrine\Migrations\Version\Version; use InvalidArgumentException; use function array_change_key_case; use function floatval; use function round; use function sprintf; use function strlen; use function strpos; use function strtolower; use function uasort; use const CASE_LOWER; final class TableMetadataStorage implements MetadataStorage { /** @var bool */ private $isInitialized; /** @var bool */ private $schemaUpToDate = false; /** @var Connection */ private $connection; /** @var AbstractSchemaManager */ private $schemaManager; /** @var AbstractPlatform */ private $platform; /** @var TableMetadataStorageConfiguration */ private $configuration; /** @var MigrationsRepository|null */ private $migrationRepository; /** @var MigrationsComparator */ private $comparator; public function __construct( Connection $connection, MigrationsComparator $comparator, ?MetadataStorageConfiguration $configuration = null, ?MigrationsRepository $migrationRepository = null ) { $this->migrationRepository = $migrationRepository; $this->connection = $connection; $this->schemaManager = $connection->getSchemaManager(); $this->platform = $connection->getDatabasePlatform(); if ($configuration !== null && ! ($configuration instanceof TableMetadataStorageConfiguration)) { throw new InvalidArgumentException(sprintf('%s accepts only %s as configuration', self::class, TableMetadataStorageConfiguration::class)); } $this->configuration = $configuration ?? new TableMetadataStorageConfiguration(); $this->comparator = $comparator; } public function getExecutedMigrations(): ExecutedMigrationsList { if (! $this->isInitialized()) { return new ExecutedMigrationsList([]); } $this->checkInitialization(); $rows = $this->connection->fetchAllAssociative(sprintf('SELECT * FROM %s', $this->configuration->getTableName())); $migrations = []; foreach ($rows as $row) { $row = array_change_key_case($row, CASE_LOWER); $version = new Version($row[strtolower($this->configuration->getVersionColumnName())]); $executedAt = $row[strtolower($this->configuration->getExecutedAtColumnName())] ?? ''; $executedAt = $executedAt !== '' ? DateTimeImmutable::createFromFormat($this->platform->getDateTimeFormatString(), $executedAt) : null; $executionTime = isset($row[strtolower($this->configuration->getExecutionTimeColumnName())]) ? floatval($row[strtolower($this->configuration->getExecutionTimeColumnName())] / 1000) : null; $migration = new ExecutedMigration( $version, $executedAt instanceof DateTimeImmutable ? $executedAt : null, $executionTime ); $migrations[(string) $version] = $migration; } uasort($migrations, function (ExecutedMigration $a, ExecutedMigration $b): int { return $this->comparator->compare($a->getVersion(), $b->getVersion()); }); return new ExecutedMigrationsList($migrations); } public function reset(): void { $this->checkInitialization(); $this->connection->executeStatement( sprintf( 'DELETE FROM %s WHERE 1 = 1', $this->platform->quoteIdentifier($this->configuration->getTableName()) ) ); } public function complete(ExecutionResult $result): void { $this->checkInitialization(); if ($result->getDirection() === Direction::DOWN) { $this->connection->delete($this->configuration->getTableName(), [ $this->configuration->getVersionColumnName() => (string) $result->getVersion(), ]); } else { $this->connection->insert($this->configuration->getTableName(), [ $this->configuration->getVersionColumnName() => (string) $result->getVersion(), $this->configuration->getExecutedAtColumnName() => $result->getExecutedAt(), $this->configuration->getExecutionTimeColumnName() => $result->getTime() === null ? null : (int) round($result->getTime() * 1000), ], [ Types::STRING, Types::DATETIME_MUTABLE, Types::INTEGER, ]); } } public function ensureInitialized(): void { if (! $this->isInitialized()) { $expectedSchemaChangelog = $this->getExpectedTable(); $this->schemaManager->createTable($expectedSchemaChangelog); $this->schemaUpToDate = true; $this->isInitialized = true; return; } $this->isInitialized = true; $expectedSchemaChangelog = $this->getExpectedTable(); $diff = $this->needsUpdate($expectedSchemaChangelog); if ($diff === null) { $this->schemaUpToDate = true; return; } $this->schemaUpToDate = true; $this->schemaManager->alterTable($diff); $this->updateMigratedVersionsFromV1orV2toV3(); } private function needsUpdate(Table $expectedTable): ?TableDiff { if ($this->schemaUpToDate) { return null; } $comparator = new Comparator(); $currentTable = $this->schemaManager->listTableDetails($this->configuration->getTableName()); $diff = $comparator->diffTable($currentTable, $expectedTable); return $diff instanceof TableDiff ? $diff : null; } private function isInitialized(): bool { if ($this->isInitialized) { return $this->isInitialized; } if ($this->connection instanceof PrimaryReadReplicaConnection) { $this->connection->ensureConnectedToPrimary(); } return $this->schemaManager->tablesExist([$this->configuration->getTableName()]); } private function checkInitialization(): void { if (! $this->isInitialized()) { throw MetadataStorageError::notInitialized(); } $expectedTable = $this->getExpectedTable(); if ($this->needsUpdate($expectedTable) !== null) { throw MetadataStorageError::notUpToDate(); } } private function getExpectedTable(): Table { $schemaChangelog = new Table($this->configuration->getTableName()); $schemaChangelog->addColumn( $this->configuration->getVersionColumnName(), 'string', ['notnull' => true, 'length' => $this->configuration->getVersionColumnLength()] ); $schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]); $schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]); $schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]); return $schemaChangelog; } private function updateMigratedVersionsFromV1orV2toV3(): void { if ($this->migrationRepository === null) { return; } $availableMigrations = $this->migrationRepository->getMigrations()->getItems(); $executedMigrations = $this->getExecutedMigrations()->getItems(); foreach ($availableMigrations as $availableMigration) { foreach ($executedMigrations as $k => $executedMigration) { if ($this->isAlreadyV3Format($availableMigration, $executedMigration)) { continue; } $this->connection->update( $this->configuration->getTableName(), [ $this->configuration->getVersionColumnName() => (string) $availableMigration->getVersion(), ], [ $this->configuration->getVersionColumnName() => (string) $executedMigration->getVersion(), ] ); unset($executedMigrations[$k]); } } } private function isAlreadyV3Format(AvailableMigration $availableMigration, ExecutedMigration $executedMigration): bool { return strpos( (string) $availableMigration->getVersion(), (string) $executedMigration->getVersion() ) !== strlen((string) $availableMigration->getVersion()) - strlen((string) $executedMigration->getVersion()); } } migrations/lib/Doctrine/Migrations/Metadata/Storage/TableMetadataStorageConfiguration.php 0000644 00000003367 15120025744 0025764 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata\Storage; final class TableMetadataStorageConfiguration implements MetadataStorageConfiguration { /** @var string */ private $tableName = 'doctrine_migration_versions'; /** @var string */ private $versionColumnName = 'version'; /** @var int */ private $versionColumnLength = 191; /** @var string */ private $executedAtColumnName = 'executed_at'; /** @var string */ private $executionTimeColumnName = 'execution_time'; public function getTableName(): string { return $this->tableName; } public function setTableName(string $tableName): void { $this->tableName = $tableName; } public function getVersionColumnName(): string { return $this->versionColumnName; } public function setVersionColumnName(string $versionColumnName): void { $this->versionColumnName = $versionColumnName; } public function getVersionColumnLength(): int { return $this->versionColumnLength; } public function setVersionColumnLength(int $versionColumnLength): void { $this->versionColumnLength = $versionColumnLength; } public function getExecutedAtColumnName(): string { return $this->executedAtColumnName; } public function setExecutedAtColumnName(string $executedAtColumnName): void { $this->executedAtColumnName = $executedAtColumnName; } public function getExecutionTimeColumnName(): string { return $this->executionTimeColumnName; } public function setExecutionTimeColumnName(string $executionTimeColumnName): void { $this->executionTimeColumnName = $executionTimeColumnName; } } migrations/lib/Doctrine/Migrations/Metadata/AvailableMigration.php 0000644 00000001404 15120025744 0021333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\Version\Version; /** * Available migrations may or may not be already executed * The migration might be already executed or not. */ final class AvailableMigration { /** @var Version */ private $version; /** @var AbstractMigration */ private $migration; public function __construct(Version $version, AbstractMigration $migration) { $this->version = $version; $this->migration = $migration; } public function getVersion(): Version { return $this->version; } public function getMigration(): AbstractMigration { return $this->migration; } } migrations/lib/Doctrine/Migrations/Metadata/AvailableMigrationsList.php 0000644 00000004022 15120025744 0022351 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Countable; use Doctrine\Migrations\Exception\MigrationNotAvailable; use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria; use Doctrine\Migrations\Version\Version; use function array_values; use function count; /** * Represents a sorted list of migrations that may or maybe not be already executed. */ final class AvailableMigrationsList implements Countable { /** @var AvailableMigration[] */ private $items = []; /** * @param AvailableMigration[] $items */ public function __construct(array $items) { $this->items = array_values($items); } /** * @return AvailableMigration[] */ public function getItems(): array { return $this->items; } public function getFirst(int $offset = 0): AvailableMigration { if (! isset($this->items[$offset])) { throw NoMigrationsFoundWithCriteria::new('first' . ($offset > 0 ? '+' . $offset : '')); } return $this->items[$offset]; } public function getLast(int $offset = 0): AvailableMigration { $offset = count($this->items) - 1 - (-1 * $offset); if (! isset($this->items[$offset])) { throw NoMigrationsFoundWithCriteria::new('last' . ($offset > 0 ? '+' . $offset : '')); } return $this->items[$offset]; } public function count(): int { return count($this->items); } public function hasMigration(Version $version): bool { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return true; } } return false; } public function getMigration(Version $version): AvailableMigration { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return $migration; } } throw MigrationNotAvailable::forVersion($version); } } migrations/lib/Doctrine/Migrations/Metadata/AvailableMigrationsSet.php 0000644 00000002564 15120025744 0022202 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Countable; use Doctrine\Migrations\Exception\MigrationNotAvailable; use Doctrine\Migrations\Version\Version; use function array_values; use function count; /** * Represents a non sorted list of migrations that may or may not be already executed. */ final class AvailableMigrationsSet implements Countable { /** @var AvailableMigration[] */ private $items = []; /** * @param AvailableMigration[] $items */ public function __construct(array $items) { $this->items = array_values($items); } /** * @return AvailableMigration[] */ public function getItems(): array { return $this->items; } public function count(): int { return count($this->items); } public function hasMigration(Version $version): bool { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return true; } } return false; } public function getMigration(Version $version): AvailableMigration { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return $migration; } } throw MigrationNotAvailable::forVersion($version); } } migrations/lib/Doctrine/Migrations/Metadata/ExecutedMigration.php 0000644 00000002001 15120025744 0021213 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use DateTimeImmutable; use Doctrine\Migrations\Version\Version; /** * Represents an already executed migration. * The migration might be not available anymore. */ final class ExecutedMigration { /** @var Version */ private $version; /** @var DateTimeImmutable|null */ private $executedAt; /** * Seconds * * @var float|null */ public $executionTime; public function __construct(Version $version, ?DateTimeImmutable $executedAt = null, ?float $executionTime = null) { $this->version = $version; $this->executedAt = $executedAt; $this->executionTime = $executionTime; } public function getExecutionTime(): ?float { return $this->executionTime; } public function getExecutedAt(): ?DateTimeImmutable { return $this->executedAt; } public function getVersion(): Version { return $this->version; } } migrations/lib/Doctrine/Migrations/Metadata/ExecutedMigrationsList.php 0000644 00000004050 15120025744 0022240 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Countable; use Doctrine\Migrations\Exception\MigrationNotExecuted; use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria; use Doctrine\Migrations\Version\Version; use function array_values; use function count; /** * Represents a sorted list of executed migrations. * The migrations in this set might be not available anymore. */ final class ExecutedMigrationsList implements Countable { /** @var ExecutedMigration[] */ private $items = []; /** * @param ExecutedMigration[] $items */ public function __construct(array $items) { $this->items = array_values($items); } /** * @return ExecutedMigration[] */ public function getItems(): array { return $this->items; } public function getFirst(int $offset = 0): ExecutedMigration { if (! isset($this->items[$offset])) { throw NoMigrationsFoundWithCriteria::new('first' . ($offset > 0 ? '+' . $offset : '')); } return $this->items[$offset]; } public function getLast(int $offset = 0): ExecutedMigration { $offset = count($this->items) - 1 - (-1 * $offset); if (! isset($this->items[$offset])) { throw NoMigrationsFoundWithCriteria::new('last' . ($offset > 0 ? '+' . $offset : '')); } return $this->items[$offset]; } public function count(): int { return count($this->items); } public function hasMigration(Version $version): bool { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return true; } } return false; } public function getMigration(Version $version): ExecutedMigration { foreach ($this->items as $migration) { if ($migration->getVersion()->equals($version)) { return $migration; } } throw MigrationNotExecuted::new((string) $version); } } migrations/lib/Doctrine/Migrations/Metadata/MigrationPlan.php 0000644 00000002547 15120025744 0020356 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\Exception\PlanAlreadyExecuted; use Doctrine\Migrations\Version\ExecutionResult; use Doctrine\Migrations\Version\Version; /** * Represents an available migration to be executed in a specific direction. */ final class MigrationPlan { /** @var string */ private $direction; /** @var Version */ private $version; /** @var AbstractMigration */ private $migration; /** @var ExecutionResult */ public $result; public function __construct(Version $version, AbstractMigration $migration, string $direction) { $this->version = $version; $this->migration = $migration; $this->direction = $direction; } public function getVersion(): Version { return $this->version; } public function getResult(): ?ExecutionResult { return $this->result; } public function markAsExecuted(ExecutionResult $result): void { if ($this->result !== null) { throw PlanAlreadyExecuted::new(); } $this->result = $result; } public function getMigration(): AbstractMigration { return $this->migration; } public function getDirection(): string { return $this->direction; } } migrations/lib/Doctrine/Migrations/Metadata/MigrationPlanList.php 0000644 00000002530 15120025744 0021202 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Metadata; use Countable; use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria; use function count; use function end; use function reset; /** * Represents a sorted list of MigrationPlan instances to execute. */ final class MigrationPlanList implements Countable { /** @var string */ private $direction; /** @var MigrationPlan[] */ private $items = []; /** * @param MigrationPlan[] $items */ public function __construct(array $items, string $direction) { $this->items = $items; $this->direction = $direction; } public function count(): int { return count($this->items); } /** * @return MigrationPlan[] */ public function getItems(): array { return $this->items; } public function getDirection(): string { return $this->direction; } public function getFirst(): MigrationPlan { if (count($this->items) === 0) { throw NoMigrationsFoundWithCriteria::new('first'); } return reset($this->items); } public function getLast(): MigrationPlan { if (count($this->items) === 0) { throw NoMigrationsFoundWithCriteria::new('last'); } return end($this->items); } } migrations/lib/Doctrine/Migrations/Provider/Exception/NoMappingFound.php 0000644 00000000501 15120025744 0022472 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider\Exception; use UnexpectedValueException; final class NoMappingFound extends UnexpectedValueException implements ProviderException { public static function new(): self { return new self('No mapping information to process'); } } migrations/lib/Doctrine/Migrations/Provider/Exception/ProviderException.php 0000644 00000000306 15120025744 0023262 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider\Exception; use Doctrine\Migrations\Exception\MigrationException; interface ProviderException extends MigrationException { } migrations/lib/Doctrine/Migrations/Provider/DBALSchemaDiffProvider.php 0000644 00000002730 15120025744 0022005 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; /** * The SchemaDiffProvider class is responsible for providing a Doctrine\DBAL\Schema\Schema instance that * represents the current state of your database. A clone of this Schema instance is passed to each of your migrations * so that you can manipulate the Schema object. Your manipulated Schema object is then compared to the original Schema * object to produce the SQL statements that need to be executed. * * @internal * * @see Doctrine\Migrations\Version\DbalExecutor */ class DBALSchemaDiffProvider implements SchemaDiffProvider { /** @var AbstractPlatform */ private $platform; /** @var AbstractSchemaManager */ private $schemaManager; public function __construct(AbstractSchemaManager $schemaManager, AbstractPlatform $platform) { $this->schemaManager = $schemaManager; $this->platform = $platform; } public function createFromSchema(): Schema { return $this->schemaManager->createSchema(); } public function createToSchema(Schema $fromSchema): Schema { return clone $fromSchema; } /** @return string[] */ public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema): array { return $fromSchema->getMigrateToSql($toSchema, $this->platform); } } migrations/lib/Doctrine/Migrations/Provider/EmptySchemaProvider.php 0000644 00000001326 15120025744 0021610 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; /** * The EmptySchemaProvider class is responsible for creating a Doctrine\DBAL\Schema\Schema instance that * represents the empty state of your database. * * @internal */ final class EmptySchemaProvider implements SchemaProvider { /** @var AbstractSchemaManager */ private $schemaManager; public function __construct(AbstractSchemaManager $schemaManager) { $this->schemaManager = $schemaManager; } public function createSchema(): Schema { return new Schema([], [], $this->schemaManager->createSchemaConfig()); } } migrations/lib/Doctrine/Migrations/Provider/LazySchemaDiffProvider.php 0000644 00000006010 15120025744 0022215 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\Schema; use ProxyManager\Configuration; use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; /** * The LazySchemaDiffProvider is responsible for lazily generating the from schema when diffing two schemas * to produce a migration. * * @internal */ class LazySchemaDiffProvider implements SchemaDiffProvider { /** @var LazyLoadingValueHolderFactory */ private $proxyFactory; /** @var SchemaDiffProvider */ private $originalSchemaManipulator; public function __construct( LazyLoadingValueHolderFactory $proxyFactory, SchemaDiffProvider $originalSchemaManipulator ) { $this->proxyFactory = $proxyFactory; $this->originalSchemaManipulator = $originalSchemaManipulator; } public static function fromDefaultProxyFactoryConfiguration( SchemaDiffProvider $originalSchemaManipulator ): LazySchemaDiffProvider { $proxyConfig = new Configuration(); $proxyConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); $proxyFactory = new LazyLoadingValueHolderFactory($proxyConfig); return new LazySchemaDiffProvider($proxyFactory, $originalSchemaManipulator); } public function createFromSchema(): Schema { $originalSchemaManipulator = $this->originalSchemaManipulator; return $this->proxyFactory->createProxy( Schema::class, static function (&$wrappedObject, $proxy, $method, array $parameters, &$initializer) use ($originalSchemaManipulator): bool { $initializer = null; $wrappedObject = $originalSchemaManipulator->createFromSchema(); return true; } ); } public function createToSchema(Schema $fromSchema): Schema { $originalSchemaManipulator = $this->originalSchemaManipulator; if ($fromSchema instanceof LazyLoadingInterface && ! $fromSchema->isProxyInitialized()) { return $this->proxyFactory->createProxy( Schema::class, static function (&$wrappedObject, $proxy, $method, array $parameters, &$initializer) use ($originalSchemaManipulator, $fromSchema): bool { $initializer = null; $wrappedObject = $originalSchemaManipulator->createToSchema($fromSchema); return true; } ); } return $this->originalSchemaManipulator->createToSchema($fromSchema); } /** @return string[] */ public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema): array { if ( $toSchema instanceof LazyLoadingInterface && ! $toSchema->isProxyInitialized() ) { return []; } return $this->originalSchemaManipulator->getSqlDiffToMigrate($fromSchema, $toSchema); } } migrations/lib/Doctrine/Migrations/Provider/OrmSchemaProvider.php 0000644 00000002744 15120025744 0021254 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Provider\Exception\NoMappingFound; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Tools\SchemaTool; use function count; use function usort; /** * The OrmSchemaProvider class is responsible for creating a Doctrine\DBAL\Schema\Schema instance from the mapping * information provided by the Doctrine ORM. This is then used to diff against your current database schema to produce * a migration to bring your database in sync with the ORM mapping information. * * @internal */ final class OrmSchemaProvider implements SchemaProvider { /** @var EntityManagerInterface */ private $entityManager; public function __construct(EntityManagerInterface $em) { $this->entityManager = $em; } /** * @throws NoMappingFound */ public function createSchema(): Schema { /** @var array<int, ClassMetadata<object>> $metadata */ $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata(); if (count($metadata) === 0) { throw NoMappingFound::new(); } usort($metadata, static function (ClassMetadata $a, ClassMetadata $b): int { return $a->getTableName() <=> $b->getTableName(); }); $tool = new SchemaTool($this->entityManager); return $tool->getSchemaFromMetadata($metadata); } } migrations/lib/Doctrine/Migrations/Provider/SchemaDiffProvider.php 0000644 00000001026 15120025744 0021357 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\Schema; /** * The SchemaDiffProvider defines the interface used to provide the from and to schemas and to produce * the SQL queries needed to migrate. * * @internal */ interface SchemaDiffProvider { public function createFromSchema(): Schema; public function createToSchema(Schema $fromSchema): Schema; /** @return string[] */ public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema): array; } migrations/lib/Doctrine/Migrations/Provider/SchemaProvider.php 0000644 00000000526 15120025744 0020572 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\Schema; /** * The SchemaProvider defines the interface used to create a Doctrine\DBAL\Schema\Schema instance that * represents the current state of your database. */ interface SchemaProvider { public function createSchema(): Schema; } migrations/lib/Doctrine/Migrations/Provider/StubSchemaProvider.php 0000644 00000001050 15120025744 0021421 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Provider; use Doctrine\DBAL\Schema\Schema; /** * The StubSchemaProvider takes a Doctrine\DBAL\Schema\Schema instance through the constructor and returns it * from the createSchema() method. */ final class StubSchemaProvider implements SchemaProvider { /** @var Schema */ private $toSchema; public function __construct(Schema $schema) { $this->toSchema = $schema; } public function createSchema(): Schema { return $this->toSchema; } } migrations/lib/Doctrine/Migrations/Query/Exception/InvalidArguments.php 0000644 00000001155 15120025744 0022403 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Query\Exception; use Doctrine\Migrations\Exception\MigrationException; use InvalidArgumentException; use function sprintf; class InvalidArguments extends InvalidArgumentException implements MigrationException { public static function wrongTypesArgumentCount(string $statement, int $parameters, int $types): self { return new self(sprintf( 'The number of types (%s) is higher than the number of passed parameters (%s) for the query "%s".', $types, $parameters, $statement )); } } migrations/lib/Doctrine/Migrations/Query/Query.php 0000644 00000002343 15120025744 0016276 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Query; use Doctrine\Migrations\Query\Exception\InvalidArguments; use function count; /** * The Query wraps the sql query, parameters and types. */ final class Query { /** @var string */ private $statement; /** @var mixed[] */ private $parameters; /** @var mixed[] */ private $types; /** * @param mixed[] $parameters * @param mixed[] $types */ public function __construct(string $statement, array $parameters = [], array $types = []) { if (count($types) > count($parameters)) { throw InvalidArguments::wrongTypesArgumentCount($statement, count($parameters), count($types)); } $this->statement = $statement; $this->parameters = $parameters; $this->types = $types; } public function __toString(): string { return $this->statement; } public function getStatement(): string { return $this->statement; } /** @return mixed[] */ public function getParameters(): array { return $this->parameters; } /** @return mixed[] */ public function getTypes(): array { return $this->types; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/CurrentCommand.php 0000644 00000003134 15120025744 0023064 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Exception\MigrationClassNotFound; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; /** * The CurrentCommand class is responsible for outputting what your current version is. */ final class CurrentCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:current'; protected function configure(): void { $this ->setAliases(['current']) ->setDescription('Outputs the current version'); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $aliasResolver = $this->getDependencyFactory()->getVersionAliasResolver(); $version = $aliasResolver->resolveVersionAlias('current'); if ((string) $version === '0') { $description = '(No migration executed yet)'; } else { try { $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); $description = $availableMigration->getMigration()->getDescription(); } catch (MigrationClassNotFound $e) { $description = '(Migration info not available)'; } } $this->io->text(sprintf( "<info>%s</info>%s\n", (string) $version, $description !== '' ? ' - ' . $description : '' )); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/DiffCommand.php 0000644 00000016227 15120025744 0022321 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Generator\Exception\NoChangesDetected; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\SqlFormatter\SqlFormatter; use OutOfBoundsException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function addslashes; use function assert; use function class_exists; use function count; use function filter_var; use function is_string; use function key; use function sprintf; use const FILTER_VALIDATE_BOOLEAN; /** * The DiffCommand class is responsible for generating a migration by comparing your current database schema to * your mapping information. */ final class DiffCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:diff'; protected function configure(): void { parent::configure(); $this ->setAliases(['diff']) ->setDescription('Generate a migration by comparing your current database to your mapping information.') ->setHelp(<<<EOT The <info>%command.name%</info> command generates a migration by comparing your current database to your mapping information: <info>%command.full_name%</info> EOT ) ->addOption( 'namespace', null, InputOption::VALUE_REQUIRED, 'The namespace to use for the migration (must be in the list of configured namespaces)' ) ->addOption( 'filter-expression', null, InputOption::VALUE_REQUIRED, 'Tables which are filtered by Regular Expression.' ) ->addOption( 'formatted', null, InputOption::VALUE_NONE, 'Format the generated SQL.' ) ->addOption( 'line-length', null, InputOption::VALUE_REQUIRED, 'Max line length of unformatted lines.', '120' ) ->addOption( 'check-database-platform', null, InputOption::VALUE_OPTIONAL, 'Check Database Platform to the generated code.', false ) ->addOption( 'allow-empty-diff', null, InputOption::VALUE_NONE, 'Do not throw an exception when no changes are detected.' ) ->addOption( 'from-empty-schema', null, InputOption::VALUE_NONE, 'Generate a full migration as if the current database was empty.' ); } /** * @throws InvalidOptionUsage */ protected function execute( InputInterface $input, OutputInterface $output ): int { $filterExpression = (string) $input->getOption('filter-expression'); if ($filterExpression === '') { $filterExpression = null; } $formatted = filter_var($input->getOption('formatted'), FILTER_VALIDATE_BOOLEAN); $lineLength = (int) $input->getOption('line-length'); $allowEmptyDiff = $input->getOption('allow-empty-diff'); $checkDbPlatform = filter_var($input->getOption('check-database-platform'), FILTER_VALIDATE_BOOLEAN); $fromEmptySchema = $input->getOption('from-empty-schema'); $namespace = $input->getOption('namespace'); if ($namespace === '') { $namespace = null; } if ($formatted) { if (! class_exists(SqlFormatter::class)) { throw InvalidOptionUsage::new( 'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require doctrine/sql-formatter".' ); } } $configuration = $this->getDependencyFactory()->getConfiguration(); $dirs = $configuration->getMigrationDirectories(); if ($namespace === null) { $namespace = key($dirs); } elseif (! isset($dirs[$namespace])) { throw new OutOfBoundsException(sprintf('Path not defined for the namespace %s', $namespace)); } assert(is_string($namespace)); $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); $newMigrations = $statusCalculator->getNewMigrations(); if (! $this->checkNewMigrationsOrExecutedUnavailable($newMigrations, $executedUnavailableMigrations, $input, $output)) { $this->io->error('Migration cancelled!'); return 3; } $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); $diffGenerator = $this->getDependencyFactory()->getDiffGenerator(); try { $path = $diffGenerator->generate( $fqcn, $filterExpression, $formatted, $lineLength, $checkDbPlatform, $fromEmptySchema ); } catch (NoChangesDetected $exception) { if ($allowEmptyDiff) { $this->io->error($exception->getMessage()); return 0; } throw $exception; } $this->io->text([ sprintf('Generated new migration class to "<info>%s</info>"', $path), '', sprintf( 'To run just this migration for testing purposes, you can use <info>migrations:execute --up \'%s\'</info>', addslashes($fqcn) ), '', sprintf( 'To revert the migration you can use <info>migrations:execute --down \'%s\'</info>', addslashes($fqcn) ), '', ]); return 0; } private function checkNewMigrationsOrExecutedUnavailable( AvailableMigrationsList $newMigrations, ExecutedMigrationsList $executedUnavailableMigrations, InputInterface $input, OutputInterface $output ): bool { if (count($newMigrations) === 0 && count($executedUnavailableMigrations) === 0) { return true; } if (count($newMigrations) !== 0) { $this->io->warning(sprintf( 'You have %d available migrations to execute.', count($newMigrations) )); } if (count($executedUnavailableMigrations) !== 0) { $this->io->warning(sprintf( 'You have %d previously executed migrations in the database that are not registered migrations.', count($executedUnavailableMigrations) )); } return $this->canExecute('Are you sure you wish to continue?', $input); } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/DoctrineCommand.php 0000644 00000011022 15120025744 0023204 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Configuration\Connection\ConfigurationFile; use Doctrine\Migrations\Configuration\Migration\ConfigurationFileWithFallback; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Tools\Console\ConsoleLogger; use Doctrine\Migrations\Tools\Console\Exception\DependenciesNotSatisfied; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function is_string; /** * The DoctrineCommand class provides base functionality for the other migrations commands to extend from. */ abstract class DoctrineCommand extends Command { /** @var DependencyFactory|null */ private $dependencyFactory; /** @var StyleInterface */ protected $io; public function __construct(?DependencyFactory $dependencyFactory = null, ?string $name = null) { $this->dependencyFactory = $dependencyFactory; parent::__construct($name); } protected function configure(): void { $this->addOption( 'configuration', null, InputOption::VALUE_REQUIRED, 'The path to a migrations configuration file. <comment>[default: any of migrations.{php,xml,json,yml,yaml}]</comment>' ); $this->addOption( 'em', null, InputOption::VALUE_REQUIRED, 'The name of the entity manager to use.' ); $this->addOption( 'conn', null, InputOption::VALUE_REQUIRED, 'The name of the connection to use.' ); if ($this->dependencyFactory !== null) { return; } $this->addOption( 'db-configuration', null, InputOption::VALUE_REQUIRED, 'The path to a database connection configuration file.', 'migrations-db.php' ); } protected function initialize(InputInterface $input, OutputInterface $output): void { $this->io = new SymfonyStyle($input, $output); $configurationParameter = $input->getOption('configuration'); if ($this->dependencyFactory === null) { $configurationLoader = new ConfigurationFileWithFallback( is_string($configurationParameter) ? $configurationParameter : null ); $connectionLoader = new ConfigurationFile($input->getOption('db-configuration')); $this->dependencyFactory = DependencyFactory::fromConnection($configurationLoader, $connectionLoader); } elseif (is_string($configurationParameter)) { $configurationLoader = new ConfigurationFileWithFallback($configurationParameter); $this->dependencyFactory->setConfigurationLoader($configurationLoader); } $this->setNamedEmOrConnection($input); if ($this->dependencyFactory->isFrozen()) { return; } $logger = new ConsoleLogger($output); $this->dependencyFactory->setService(LoggerInterface::class, $logger); $this->dependencyFactory->freeze(); } protected function getDependencyFactory(): DependencyFactory { if ($this->dependencyFactory === null) { throw DependenciesNotSatisfied::new(); } return $this->dependencyFactory; } protected function canExecute(string $question, InputInterface $input): bool { return ! $input->isInteractive() || $this->io->confirm($question); } private function setNamedEmOrConnection(InputInterface $input): void { $emName = $input->getOption('em'); $connName = $input->getOption('conn'); if ($emName !== null && $connName !== null) { throw new InvalidOptionUsage('You can specify only one of the --em and --conn options.'); } if ($this->dependencyFactory->hasEntityManager() && $emName !== null) { $this->dependencyFactory->getConfiguration()->setEntityManagerName($emName); return; } if ($connName !== null) { $this->dependencyFactory->getConfiguration()->setConnectionName($connName); return; } } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/DumpSchemaCommand.php 0000644 00000011341 15120025744 0023467 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\Migrations\Tools\Console\Exception\SchemaDumpRequiresNoMigrations; use Doctrine\SqlFormatter\SqlFormatter; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function addslashes; use function assert; use function class_exists; use function is_string; use function key; use function sprintf; use function strpos; /** * The DumpSchemaCommand class is responsible for dumping your current database schema to a migration class. This is * intended to be used in conjunction with the RollupCommand. * * @see Doctrine\Migrations\Tools\Console\Command\RollupCommand */ final class DumpSchemaCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:dump-schema'; protected function configure(): void { parent::configure(); $this ->setAliases(['dump-schema']) ->setDescription('Dump the schema for your database to a migration.') ->setHelp(<<<EOT The <info>%command.name%</info> command dumps the schema for your database to a migration: <info>%command.full_name%</info> After dumping your schema to a migration, you can rollup your migrations using the <info>migrations:rollup</info> command. EOT ) ->addOption( 'formatted', null, InputOption::VALUE_NONE, 'Format the generated SQL.' ) ->addOption( 'namespace', null, InputOption::VALUE_REQUIRED, 'Namespace to use for the generated migrations (defaults to the first namespace definition).' ) ->addOption( 'filter-tables', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter the tables to dump via Regex.' ) ->addOption( 'line-length', null, InputOption::VALUE_OPTIONAL, 'Max line length of unformatted lines.', '120' ); } /** * @throws SchemaDumpRequiresNoMigrations */ public function execute( InputInterface $input, OutputInterface $output ): int { $formatted = $input->getOption('formatted'); $lineLength = (int) $input->getOption('line-length'); $schemaDumper = $this->getDependencyFactory()->getSchemaDumper(); if ($formatted) { if (! class_exists(SqlFormatter::class)) { throw InvalidOptionUsage::new( 'The "--formatted" option can only be used if the sql formatter is installed. Please run "composer require doctrine/sql-formatter".' ); } } $configuration = $this->getDependencyFactory()->getConfiguration(); $namespace = $input->getOption('namespace'); if ($namespace === null) { $dirs = $configuration->getMigrationDirectories(); $namespace = key($dirs); } assert(is_string($namespace)); $this->checkNoPreviousDumpExistsForNamespace($namespace); $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); $path = $schemaDumper->dump( $fqcn, $input->getOption('filter-tables'), $formatted, $lineLength ); $this->io->text([ sprintf('Dumped your schema to a new migration class at "<info>%s</info>"', $path), '', sprintf( 'To run just this migration for testing purposes, you can use <info>migrations:execute --up \'%s\'</info>', addslashes($fqcn) ), '', sprintf( 'To revert the migration you can use <info>migrations:execute --down \'%s\'</info>', addslashes($fqcn) ), '', 'To use this as a rollup migration you can use the <info>migrations:rollup</info> command.', '', ]); return 0; } private function checkNoPreviousDumpExistsForNamespace(string $namespace): void { $migrations = $this->getDependencyFactory()->getMigrationRepository()->getMigrations(); foreach ($migrations->getItems() as $migration) { if (strpos((string) $migration->getVersion(), $namespace) !== false) { throw SchemaDumpRequiresNoMigrations::new($namespace); } } } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/ExecuteCommand.php 0000644 00000012703 15120025744 0023046 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Version\Direction; use Doctrine\Migrations\Version\Version; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_map; use function getcwd; use function implode; use function is_string; use function is_writable; use function sprintf; /** * The ExecuteCommand class is responsible for executing migration versions up or down manually. */ final class ExecuteCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:execute'; protected function configure(): void { $this ->setAliases(['execute']) ->setDescription( 'Execute one or more migration versions up or down manually.' ) ->addArgument( 'versions', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The versions to execute.', null ) ->addOption( 'write-sql', null, InputOption::VALUE_OPTIONAL, 'The path to output the migration SQL file. Defaults to current working directory.', false ) ->addOption( 'dry-run', null, InputOption::VALUE_NONE, 'Execute the migration as a dry run.' ) ->addOption( 'up', null, InputOption::VALUE_NONE, 'Execute the migration up.' ) ->addOption( 'down', null, InputOption::VALUE_NONE, 'Execute the migration down.' ) ->addOption( 'query-time', null, InputOption::VALUE_NONE, 'Time all the queries individually.' ) ->setHelp(<<<EOT The <info>%command.name%</info> command executes migration versions up or down manually: <info>%command.full_name% FQCN</info> You can show more information about the process by increasing the verbosity level. To see the executed queries, set the level to debug with <comment>-vv</comment>: <info>%command.full_name% FQCN -vv</info> If no <comment>--up</comment> or <comment>--down</comment> option is specified it defaults to up: <info>%command.full_name% FQCN --down</info> You can also execute the migration as a <comment>--dry-run</comment>: <info>%command.full_name% FQCN --dry-run</info> You can output the prepared SQL statements to a file with <comment>--write-sql</comment>: <info>%command.full_name% FQCN --write-sql</info> Or you can also execute the migration without a warning message which you need to interact with: <info>%command.full_name% FQCN --no-interaction</info> All the previous commands accept multiple migration versions, allowing you run execute more than one migration at once: <info>%command.full_name% FQCN-1 FQCN-2 ...FQCN-n </info> EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory(); $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input); $question = sprintf( 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', $this->getDependencyFactory()->getConnection()->getDatabase() ?? '<unnamed>' ); if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) { $this->io->error('Migration cancelled!'); return 1; } $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); $versions = $input->getArgument('versions'); $direction = $input->getOption('down') !== false ? Direction::DOWN : Direction::UP; $path = $input->getOption('write-sql') ?? getcwd(); if (is_string($path) && ! is_writable($path)) { $this->io->error(sprintf('The path "%s" not writeable!', $path)); return 1; } $planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator(); $plan = $planCalculator->getPlanForVersions(array_map(static function (string $version): Version { return new Version($version); }, $versions), $direction); $this->getDependencyFactory()->getLogger()->notice( 'Executing' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {versions} {direction}', [ 'direction' => $plan->getDirection(), 'versions' => implode(', ', $versions), ] ); $migrator = $this->getDependencyFactory()->getMigrator(); $sql = $migrator->migrate($plan, $migratorConfiguration); if (is_string($path)) { $writer = $this->getDependencyFactory()->getQueryWriter(); $writer->write($path, $direction, $sql); } $this->io->newLine(); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/GenerateCommand.php 0000644 00000005074 15120025744 0023201 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Exception; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function assert; use function is_string; use function key; use function sprintf; /** * The GenerateCommand class is responsible for generating a blank migration class for you to modify to your needs. */ final class GenerateCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:generate'; protected function configure(): void { $this ->setAliases(['generate']) ->setDescription('Generate a blank migration class.') ->addOption( 'namespace', null, InputOption::VALUE_REQUIRED, 'The namespace to use for the migration (must be in the list of configured namespaces)' ) ->setHelp(<<<EOT The <info>%command.name%</info> command generates a blank migration class: <info>%command.full_name%</info> EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $configuration = $this->getDependencyFactory()->getConfiguration(); $migrationGenerator = $this->getDependencyFactory()->getMigrationGenerator(); $namespace = $input->getOption('namespace'); if ($namespace === '') { $namespace = null; } $dirs = $configuration->getMigrationDirectories(); if ($namespace === null) { $namespace = key($dirs); } elseif (! isset($dirs[$namespace])) { throw new Exception(sprintf('Path not defined for the namespace %s', $namespace)); } assert(is_string($namespace)); $fqcn = $this->getDependencyFactory()->getClassNameGenerator()->generateClassName($namespace); $path = $migrationGenerator->generateMigration($fqcn); $this->io->text([ sprintf('Generated new migration class to "<info>%s</info>"', $path), '', sprintf( 'To run just this migration for testing purposes, you can use <info>migrations:execute --up \'%s\'</info>', $fqcn ), '', sprintf( 'To revert the migration you can use <info>migrations:execute --down \'%s\'</info>', $fqcn ), '', ]); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/LatestCommand.php 0000644 00000002705 15120025744 0022701 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Exception\NoMigrationsToExecute; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; /** * The LatestCommand class is responsible for outputting what your latest version is. */ final class LatestCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:latest'; protected function configure(): void { $this ->setAliases(['latest']) ->setDescription('Outputs the latest version'); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $aliasResolver = $this->getDependencyFactory()->getVersionAliasResolver(); try { $version = $aliasResolver->resolveVersionAlias('latest'); $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); $description = $availableMigration->getMigration()->getDescription(); } catch (NoMigrationsToExecute $e) { $version = '0'; $description = ''; } $this->io->text(sprintf( "<info>%s</info>%s\n", $version, $description !== '' ? ' - ' . $description : '' )); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/ListCommand.php 0000644 00000005163 15120025744 0022361 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Version\Version; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function array_map; use function array_merge; use function array_unique; use function uasort; /** * The ListCommand class is responsible for outputting a list of all available migrations and their status. */ final class ListCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:list'; protected function configure(): void { $this ->setAliases(['list-migrations']) ->setDescription('Display a list of all available migrations and their status.') ->setHelp(<<<EOT The <info>%command.name%</info> command outputs a list of all available migrations and their status: <info>%command.full_name%</info> EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $versions = $this->getSortedVersions( $this->getDependencyFactory()->getMigrationPlanCalculator()->getMigrations(), // available migrations $this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations() // executed migrations ); $this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output); return 0; } /** * @return Version[] */ private function getSortedVersions(AvailableMigrationsList $availableMigrations, ExecutedMigrationsList $executedMigrations): array { $availableVersions = array_map(static function (AvailableMigration $availableMigration): Version { return $availableMigration->getVersion(); }, $availableMigrations->getItems()); $executedVersions = array_map(static function (ExecutedMigration $executedMigration): Version { return $executedMigration->getVersion(); }, $executedMigrations->getItems()); $versions = array_unique(array_merge($availableVersions, $executedVersions)); $comparator = $this->getDependencyFactory()->getVersionComparator(); uasort($versions, static function (Version $a, Version $b) use ($comparator): int { return $comparator->compare($a, $b); }); return $versions; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/MigrateCommand.php 0000644 00000023447 15120025744 0023043 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria; use Doctrine\Migrations\Exception\NoMigrationsToExecute; use Doctrine\Migrations\Exception\UnknownMigrationVersion; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function count; use function getcwd; use function in_array; use function is_string; use function is_writable; use function sprintf; use function strpos; /** * The MigrateCommand class is responsible for executing a migration from the current version to another * version up or down. It will calculate all the migration versions that need to be executed and execute them. */ final class MigrateCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:migrate'; protected function configure(): void { $this ->setAliases(['migrate']) ->setDescription( 'Execute a migration to a specified version or the latest available version.' ) ->addArgument( 'version', InputArgument::OPTIONAL, 'The version FQCN or alias (first, prev, next, latest) to migrate to.', 'latest' ) ->addOption( 'write-sql', null, InputOption::VALUE_OPTIONAL, 'The path to output the migration SQL file. Defaults to current working directory.', false ) ->addOption( 'dry-run', null, InputOption::VALUE_NONE, 'Execute the migration as a dry run.' ) ->addOption( 'query-time', null, InputOption::VALUE_NONE, 'Time all the queries individually.' ) ->addOption( 'allow-no-migration', null, InputOption::VALUE_NONE, 'Do not throw an exception if no migration is available.' ) ->addOption( 'all-or-nothing', null, InputOption::VALUE_OPTIONAL, 'Wrap the entire migration in a transaction.' ) ->setHelp(<<<EOT The <info>%command.name%</info> command executes a migration to a specified version or the latest available version: <info>%command.full_name%</info> You can show more information about the process by increasing the verbosity level. To see the executed queries, set the level to debug with <comment>-vv</comment>: <info>%command.full_name% -vv</info> You can optionally manually specify the version you wish to migrate to: <info>%command.full_name% FQCN</info> You can specify the version you wish to migrate to using an alias: <info>%command.full_name% prev</info> <info>These alias are defined : first, latest, prev, current and next</info> You can specify the version you wish to migrate to using an number against the current version: <info>%command.full_name% current+3</info> You can also execute the migration as a <comment>--dry-run</comment>: <info>%command.full_name% FQCN --dry-run</info> You can output the prepared SQL statements to a file with <comment>--write-sql</comment>: <info>%command.full_name% FQCN --write-sql</info> Or you can also execute the migration without a warning message which you need to interact with: <info>%command.full_name% --no-interaction</info> You can also time all the different queries if you wanna know which one is taking so long: <info>%command.full_name% --query-time</info> Use the --all-or-nothing option to wrap the entire migration in a transaction. EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $migratorConfigurationFactory = $this->getDependencyFactory()->getConsoleInputMigratorConfigurationFactory(); $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input); $question = sprintf( 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', $this->getDependencyFactory()->getConnection()->getDatabase() ?? '<unnamed>' ); if (! $migratorConfiguration->isDryRun() && ! $this->canExecute($question, $input)) { $this->io->error('Migration cancelled!'); return 3; } $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); $allowNoMigration = $input->getOption('allow-no-migration'); $versionAlias = $input->getArgument('version'); $path = $input->getOption('write-sql') ?? getcwd(); if (is_string($path) && ! is_writable($path)) { $this->io->error(sprintf('The path "%s" not writeable!', $path)); return 1; } $migrationRepository = $this->getDependencyFactory()->getMigrationRepository(); if (count($migrationRepository->getMigrations()) === 0) { $message = sprintf( 'The version "%s" couldn\'t be reached, there are no registered migrations.', $versionAlias ); if ($allowNoMigration) { $this->io->warning($message); return 0; } $this->io->error($message); return 1; } try { $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias($versionAlias); } catch (UnknownMigrationVersion $e) { $this->io->error(sprintf( 'Unknown version: %s', OutputFormatter::escape($versionAlias) )); return 1; } catch (NoMigrationsToExecute | NoMigrationsFoundWithCriteria $e) { return $this->exitForAlias($versionAlias); } $planCalculator = $this->getDependencyFactory()->getMigrationPlanCalculator(); $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); if ($this->checkExecutedUnavailableMigrations($executedUnavailableMigrations, $input) === false) { return 3; } $plan = $planCalculator->getPlanUntilVersion($version); if (count($plan) === 0) { return $this->exitForAlias($versionAlias); } $this->getDependencyFactory()->getLogger()->notice( 'Migrating' . ($migratorConfiguration->isDryRun() ? ' (dry-run)' : '') . ' {direction} to {to}', [ 'direction' => $plan->getDirection(), 'to' => (string) $version, ] ); $migrator = $this->getDependencyFactory()->getMigrator(); $sql = $migrator->migrate($plan, $migratorConfiguration); if (is_string($path)) { $writer = $this->getDependencyFactory()->getQueryWriter(); $writer->write($path, $plan->getDirection(), $sql); } $this->io->newLine(); return 0; } private function checkExecutedUnavailableMigrations( ExecutedMigrationsList $executedUnavailableMigrations, InputInterface $input ): bool { if (count($executedUnavailableMigrations) !== 0) { $this->io->warning(sprintf( 'You have %s previously executed migrations in the database that are not registered migrations.', count($executedUnavailableMigrations) )); foreach ($executedUnavailableMigrations->getItems() as $executedUnavailableMigration) { $this->io->text(sprintf( '<comment>>></comment> %s (<comment>%s</comment>)', $executedUnavailableMigration->getExecutedAt() !== null ? $executedUnavailableMigration->getExecutedAt()->format('Y-m-d H:i:s') : null, $executedUnavailableMigration->getVersion() )); } $question = 'Are you sure you wish to continue?'; if (! $this->canExecute($question, $input)) { $this->io->error('Migration cancelled!'); return false; } } return true; } private function exitForAlias(string $versionAlias): int { $version = $this->getDependencyFactory()->getVersionAliasResolver()->resolveVersionAlias('current'); // Allow meaningful message when latest version already reached. if (in_array($versionAlias, ['current', 'latest', 'first'], true)) { $message = sprintf( 'Already at the %s version ("%s")', $versionAlias, (string) $version ); $this->io->success($message); } elseif (in_array($versionAlias, ['next', 'prev'], true) || strpos($versionAlias, 'current') === 0) { $message = sprintf( 'The version "%s" couldn\'t be reached, you are at version "%s"', $versionAlias, (string) $version ); $this->io->error($message); } else { $message = sprintf( 'You are already at version "%s"', (string) $version ); $this->io->success($message); } return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/RollupCommand.php 0000644 00000004024 15120025744 0022716 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; /** * The RollupCommand class is responsible for deleting all previously executed migrations from the versions table * and marking the freshly dumped schema migration (that was created with DumpSchemaCommand) as migrated. */ final class RollupCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:rollup'; protected function configure(): void { parent::configure(); $this ->setAliases(['rollup']) ->setDescription('Rollup migrations by deleting all tracked versions and insert the one version that exists.') ->setHelp(<<<EOT The <info>%command.name%</info> command rolls up migrations by deleting all tracked versions and inserts the one version that exists that was created with the <info>migrations:dump-schema</info> command. <info>%command.full_name%</info> To dump your schema to a migration version you can use the <info>migrations:dump-schema</info> command. EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $question = sprintf( 'WARNING! You are about to execute a migration in database "%s" that could result in schema changes and data loss. Are you sure you wish to continue?', $this->getDependencyFactory()->getConnection()->getDatabase() ?? '<unnamed>' ); if (! $this->canExecute($question, $input)) { $this->io->error('Migration cancelled!'); return 3; } $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); $version = $this->getDependencyFactory()->getRollup()->rollup(); $this->io->success(sprintf( 'Rolled up migrations to version %s', (string) $version )); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/StatusCommand.php 0000644 00000002266 15120025744 0022732 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * The StatusCommand class is responsible for outputting what the current state is of all your migrations. It shows * what your current version is, how many new versions you have to execute, etc. and details about each of your migrations. */ final class StatusCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:status'; protected function configure(): void { $this ->setAliases(['status']) ->setDescription('View the status of a set of migrations.') ->setHelp(<<<EOT The <info>%command.name%</info> command outputs the status of a set of migrations: <info>%command.full_name%</info> EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $infosHelper = $this->getDependencyFactory()->getMigrationStatusInfosHelper(); $infosHelper->showMigrationsInfo($output); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/SyncMetadataCommand.php 0000644 00000002015 15120025744 0024014 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; final class SyncMetadataCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:sync-metadata-storage'; protected function configure(): void { parent::configure(); $this ->setAliases(['sync-metadata-storage']) ->setDescription('Ensures that the metadata storage is at the latest version.') ->setHelp(<<<EOT The <info>%command.name%</info> command updates metadata storage the latest version. <info>%command.full_name%</info> EOT ); } public function execute( InputInterface $input, OutputInterface $output ): int { $this->getDependencyFactory()->getMetadataStorage()->ensureInitialized(); $this->io->success('Metadata storage synchronized'); return 0; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/UpToDateCommand.php 0000644 00000010546 15120025744 0023134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Version\Version; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_map; use function array_merge; use function array_unique; use function count; use function sprintf; use function uasort; /** * The UpToDateCommand class outputs if your database is up to date or if there are new migrations * that need to be executed. */ final class UpToDateCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:up-to-date'; protected function configure(): void { $this ->setAliases(['up-to-date']) ->setDescription('Tells you if your schema is up-to-date.') ->addOption('fail-on-unregistered', 'u', InputOption::VALUE_NONE, 'Whether to fail when there are unregistered extra migrations found') ->addOption('list-migrations', 'l', InputOption::VALUE_NONE, 'Show a list of missing or not migrated versions.') ->setHelp(<<<EOT The <info>%command.name%</info> command tells you if your schema is up-to-date: <info>%command.full_name%</info> EOT ); parent::configure(); } protected function execute(InputInterface $input, OutputInterface $output): int { $statusCalculator = $this->getDependencyFactory()->getMigrationStatusCalculator(); $executedUnavailableMigrations = $statusCalculator->getExecutedUnavailableMigrations(); $newMigrations = $statusCalculator->getNewMigrations(); $newMigrationsCount = count($newMigrations); $executedUnavailableMigrationsCount = count($executedUnavailableMigrations); if ($newMigrationsCount === 0 && $executedUnavailableMigrationsCount === 0) { $this->io->success('Up-to-date! No migrations to execute.'); return 0; } $exitCode = 0; if ($newMigrationsCount > 0) { $this->io->error(sprintf( 'Out-of-date! %u migration%s available to execute.', $newMigrationsCount, $newMigrationsCount > 1 ? 's are' : ' is' )); $exitCode = 1; } if ($executedUnavailableMigrationsCount > 0) { $this->io->error(sprintf( 'You have %1$u previously executed migration%3$s in the database that %2$s registered migration%3$s.', $executedUnavailableMigrationsCount, $executedUnavailableMigrationsCount > 1 ? 'are not' : 'is not a', $executedUnavailableMigrationsCount > 1 ? 's' : '' )); if ($input->getOption('fail-on-unregistered')) { $exitCode = 2; } } if ($input->getOption('list-migrations')) { $versions = $this->getSortedVersions($newMigrations, $executedUnavailableMigrations); $this->getDependencyFactory()->getMigrationStatusInfosHelper()->listVersions($versions, $output); $this->io->newLine(); } return $exitCode; } /** * @return Version[] */ private function getSortedVersions(AvailableMigrationsList $newMigrations, ExecutedMigrationsList $executedUnavailableMigrations): array { $executedUnavailableVersion = array_map(static function (ExecutedMigration $executedMigration): Version { return $executedMigration->getVersion(); }, $executedUnavailableMigrations->getItems()); $newVersions = array_map(static function (AvailableMigration $availableMigration): Version { return $availableMigration->getVersion(); }, $newMigrations->getItems()); $versions = array_unique(array_merge($executedUnavailableVersion, $newVersions)); $comparator = $this->getDependencyFactory()->getVersionComparator(); uasort($versions, static function (Version $a, Version $b) use ($comparator): int { return $comparator->compare($a, $b); }); return $versions; } } migrations/lib/Doctrine/Migrations/Tools/Console/Command/VersionCommand.php 0000644 00000023026 15120025744 0023071 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Command; use Doctrine\Migrations\Exception\MigrationClassNotFound; use Doctrine\Migrations\Exception\UnknownMigrationVersion; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Tools\Console\Exception\InvalidOptionUsage; use Doctrine\Migrations\Tools\Console\Exception\VersionAlreadyExists; use Doctrine\Migrations\Tools\Console\Exception\VersionDoesNotExist; use Doctrine\Migrations\Version\Direction; use Doctrine\Migrations\Version\ExecutionResult; use Doctrine\Migrations\Version\Version; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; /** * The VersionCommand class is responsible for manually adding and deleting migration versions from the tracking table. */ final class VersionCommand extends DoctrineCommand { /** @var string */ protected static $defaultName = 'migrations:version'; /** @var bool */ private $markMigrated; protected function configure(): void { $this ->setAliases(['version']) ->setDescription('Manually add and delete migration versions from the version table.') ->addArgument( 'version', InputArgument::OPTIONAL, 'The version to add or delete.', null ) ->addOption( 'add', null, InputOption::VALUE_NONE, 'Add the specified version.' ) ->addOption( 'delete', null, InputOption::VALUE_NONE, 'Delete the specified version.' ) ->addOption( 'all', null, InputOption::VALUE_NONE, 'Apply to all the versions.' ) ->addOption( 'range-from', null, InputOption::VALUE_OPTIONAL, 'Apply from specified version.' ) ->addOption( 'range-to', null, InputOption::VALUE_OPTIONAL, 'Apply to specified version.' ) ->setHelp(<<<EOT The <info>%command.name%</info> command allows you to manually add, delete or synchronize migration versions from the version table: <info>%command.full_name% MIGRATION-FQCN --add</info> If you want to delete a version you can use the <comment>--delete</comment> option: <info>%command.full_name% MIGRATION-FQCN --delete</info> If you want to synchronize by adding or deleting all migration versions available in the version table you can use the <comment>--all</comment> option: <info>%command.full_name% --add --all</info> <info>%command.full_name% --delete --all</info> If you want to synchronize by adding or deleting some range of migration versions available in the version table you can use the <comment>--range-from/--range-to</comment> option: <info>%command.full_name% --add --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN</info> <info>%command.full_name% --delete --range-from=MIGRATION-FQCN --range-to=MIGRATION-FQCN</info> You can also execute this command without a warning message which you need to interact with: <info>%command.full_name% --no-interaction</info> EOT ); parent::configure(); } /** * @throws InvalidOptionUsage */ protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getOption('add') === false && $input->getOption('delete') === false) { throw InvalidOptionUsage::new('You must specify whether you want to --add or --delete the specified version.'); } $this->markMigrated = $input->getOption('add'); if ($input->isInteractive()) { $question = 'WARNING! You are about to add, delete or synchronize migration versions from the version table that could result in data lost. Are you sure you wish to continue?'; $confirmation = $this->io->confirm($question); if ($confirmation) { $this->markVersions($input, $output); } else { $this->io->error('Migration cancelled!'); } } else { $this->markVersions($input, $output); } return 0; } /** * @throws InvalidOptionUsage */ private function markVersions(InputInterface $input, OutputInterface $output): void { $affectedVersion = $input->getArgument('version'); $allOption = $input->getOption('all'); $rangeFromOption = $input->getOption('range-from'); $rangeToOption = $input->getOption('range-to'); if ($allOption === true && ($rangeFromOption !== null || $rangeToOption !== null)) { throw InvalidOptionUsage::new( 'Options --all and --range-to/--range-from both used. You should use only one of them.' ); } if ($rangeFromOption !== null xor $rangeToOption !== null) { throw InvalidOptionUsage::new( 'Options --range-to and --range-from should be used together.' ); } $executedMigrations = $this->getDependencyFactory()->getMetadataStorage()->getExecutedMigrations(); $availableVersions = $this->getDependencyFactory()->getMigrationPlanCalculator()->getMigrations(); if ($allOption === true) { if ($input->getOption('delete') === true) { foreach ($executedMigrations->getItems() as $availableMigration) { $this->mark($input, $output, $availableMigration->getVersion(), false, $executedMigrations); } } foreach ($availableVersions->getItems() as $availableMigration) { $this->mark($input, $output, $availableMigration->getVersion(), true, $executedMigrations); } } elseif ($affectedVersion !== null) { $this->mark($input, $output, new Version($affectedVersion), false, $executedMigrations); } elseif ($rangeFromOption !== null && $rangeToOption !== null) { $migrate = false; foreach ($availableVersions->getItems() as $availableMigration) { if ((string) $availableMigration->getVersion() === $rangeFromOption) { $migrate = true; } if ($migrate) { $this->mark($input, $output, $availableMigration->getVersion(), true, $executedMigrations); } if ((string) $availableMigration->getVersion() === $rangeToOption) { break; } } } else { throw InvalidOptionUsage::new('You must specify the version or use the --all argument.'); } } /** * @throws VersionAlreadyExists * @throws VersionDoesNotExist * @throws UnknownMigrationVersion */ private function mark(InputInterface $input, OutputInterface $output, Version $version, bool $all, ExecutedMigrationsList $executedMigrations): void { try { $availableMigration = $this->getDependencyFactory()->getMigrationRepository()->getMigration($version); } catch (MigrationClassNotFound $e) { $availableMigration = null; } $storage = $this->getDependencyFactory()->getMetadataStorage(); if ($availableMigration === null) { if ($input->getOption('delete') === false) { throw UnknownMigrationVersion::new((string) $version); } $question = 'WARNING! You are about to delete a migration version from the version table that has no corresponding migration file.' . 'Do you want to delete this migration from the migrations table?'; $confirmation = $this->io->confirm($question); if ($confirmation) { $migrationResult = new ExecutionResult($version, Direction::DOWN); $storage->complete($migrationResult); $this->io->text(sprintf( "<info>%s</info> deleted from the version table.\n", (string) $version )); return; } } $marked = false; if ($this->markMigrated && $executedMigrations->hasMigration($version)) { if (! $all) { throw VersionAlreadyExists::new($version); } $marked = true; } if (! $this->markMigrated && ! $executedMigrations->hasMigration($version)) { if (! $all) { throw VersionDoesNotExist::new($version); } $marked = true; } if ($marked === true) { return; } if ($this->markMigrated) { $migrationResult = new ExecutionResult($version, Direction::UP); $storage->complete($migrationResult); $this->io->text(sprintf( "<info>%s</info> added to the version table.\n", (string) $version )); } else { $migrationResult = new ExecutionResult($version, Direction::DOWN); $storage->complete($migrationResult); $this->io->text(sprintf( "<info>%s</info> deleted from the version table.\n", (string) $version )); } } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/ConsoleException.php 0000644 00000000312 15120025744 0023777 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use Doctrine\Migrations\Exception\MigrationException; interface ConsoleException extends MigrationException { } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/DependenciesNotSatisfied.php 0000644 00000000526 15120025744 0025430 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use LogicException; final class DependenciesNotSatisfied extends LogicException implements ConsoleException { public static function new(): self { return new self('The dependency factory has not been initialized or provided.'); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/DirectoryDoesNotExist.php 0000644 00000000621 15120025744 0024776 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use InvalidArgumentException; use function sprintf; final class DirectoryDoesNotExist extends InvalidArgumentException implements ConsoleException { public static function new(string $directory): self { return new self(sprintf('Migrations directory "%s" does not exist.', $directory)); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/FileTypeNotSupported.php 0000644 00000000521 15120025744 0024630 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use InvalidArgumentException; final class FileTypeNotSupported extends InvalidArgumentException implements ConsoleException { public static function new(): self { return new self('Given config file type is not supported'); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/InvalidOptionUsage.php 0000644 00000000505 15120025744 0024266 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use InvalidArgumentException; final class InvalidOptionUsage extends InvalidArgumentException implements ConsoleException { public static function new(string $explanation): self { return new self($explanation); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/SchemaDumpRequiresNoMigrations.php 0000644 00000000723 15120025744 0026624 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use RuntimeException; use function sprintf; final class SchemaDumpRequiresNoMigrations extends RuntimeException implements ConsoleException { public static function new(string $namespace): self { return new self(sprintf( 'Delete any previous migrations in the namespace "%s" before dumping your schema.', $namespace )); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/VersionAlreadyExists.php 0000644 00000000713 15120025744 0024652 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use Doctrine\Migrations\Version\Version; use InvalidArgumentException; use function sprintf; final class VersionAlreadyExists extends InvalidArgumentException implements ConsoleException { public static function new(Version $version): self { return new self(sprintf('The version "%s" already exists in the version table.', (string) $version)); } } migrations/lib/Doctrine/Migrations/Tools/Console/Exception/VersionDoesNotExist.php 0000644 00000000712 15120025744 0024460 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Exception; use Doctrine\Migrations\Version\Version; use InvalidArgumentException; use function sprintf; final class VersionDoesNotExist extends InvalidArgumentException implements ConsoleException { public static function new(Version $version): self { return new self(sprintf('The version "%s" does not exist in the version table.', (string) $version)); } } migrations/lib/Doctrine/Migrations/Tools/Console/Helper/ConfigurationHelper.php 0000644 00000000667 15120025744 0023763 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Helper; use Doctrine\Migrations\Configuration\Configuration; use Symfony\Component\Console\Input\InputInterface; /** * The ConfigurationHelper defines the interface for getting the Configuration instance to be used for migrations. */ interface ConfigurationHelper { public function getConfiguration( InputInterface $input ): Configuration; } migrations/lib/Doctrine/Migrations/Tools/Console/Helper/MigrationDirectoryHelper.php 0000644 00000002552 15120025744 0024765 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Helper; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Tools\Console\Exception\DirectoryDoesNotExist; use function date; use function file_exists; use function mkdir; use function rtrim; use const DIRECTORY_SEPARATOR; /** * The MigrationDirectoryHelper class is responsible for returning the directory that migrations are stored in. * * @internal */ class MigrationDirectoryHelper { /** * @throws DirectoryDoesNotExist */ public function getMigrationDirectory(Configuration $configuration, string $dir): string { $dir = rtrim($dir, '/'); if (! file_exists($dir)) { throw DirectoryDoesNotExist::new($dir); } if ($configuration->areMigrationsOrganizedByYear()) { $dir .= $this->appendDir(date('Y')); } if ($configuration->areMigrationsOrganizedByYearAndMonth()) { $dir .= $this->appendDir(date('m')); } $this->createDirIfNotExists($dir); return $dir; } private function appendDir(string $dir): string { return DIRECTORY_SEPARATOR . $dir; } private function createDirIfNotExists(string $dir): void { if (file_exists($dir)) { return; } mkdir($dir, 0755, true); } } migrations/lib/Doctrine/Migrations/Tools/Console/Helper/MigrationStatusInfosHelper.php 0000644 00000017550 15120025744 0025307 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console\Helper; use DateTimeInterface; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\Migrations\Version\AliasResolver; use Doctrine\Migrations\Version\MigrationPlanCalculator; use Doctrine\Migrations\Version\MigrationStatusCalculator; use Doctrine\Migrations\Version\Version; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Output\OutputInterface; use Throwable; use function array_unshift; use function count; use function get_class; use function sprintf; /** * The MigrationStatusInfosHelper class is responsible for building the array of information used when displaying * the status of your migrations. * * @internal * * @see Doctrine\Migrations\Tools\Console\Command\StatusCommand */ class MigrationStatusInfosHelper { /** @var Configuration */ private $configuration; /** @var Connection */ private $connection; /** @var AliasResolver */ private $aliasResolver; /** @var MetadataStorage */ private $metadataStorage; /** @var MigrationPlanCalculator */ private $migrationPlanCalculator; /** @var MigrationStatusCalculator */ private $statusCalculator; public function __construct( Configuration $configuration, Connection $connection, AliasResolver $aliasResolver, MigrationPlanCalculator $migrationPlanCalculator, MigrationStatusCalculator $statusCalculator, MetadataStorage $metadataStorage ) { $this->configuration = $configuration; $this->connection = $connection; $this->aliasResolver = $aliasResolver; $this->migrationPlanCalculator = $migrationPlanCalculator; $this->metadataStorage = $metadataStorage; $this->statusCalculator = $statusCalculator; } /** * @param Version[] $versions */ public function listVersions(array $versions, OutputInterface $output): void { $table = new Table($output); $table->setHeaders( [ [new TableCell('Migration Versions', ['colspan' => 4])], ['Migration', 'Status', 'Migrated At', 'Execution Time', 'Description'], ] ); $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $availableMigrations = $this->migrationPlanCalculator->getMigrations(); foreach ($versions as $version) { $description = null; $executedAt = null; $executionTime = null; if ($executedMigrations->hasMigration($version)) { $executedMigration = $executedMigrations->getMigration($version); $executionTime = $executedMigration->getExecutionTime(); $executedAt = $executedMigration->getExecutedAt() instanceof DateTimeInterface ? $executedMigration->getExecutedAt()->format('Y-m-d H:i:s') : null; } if ($availableMigrations->hasMigration($version)) { $description = $availableMigrations->getMigration($version)->getMigration()->getDescription(); } if ($executedMigrations->hasMigration($version) && $availableMigrations->hasMigration($version)) { $status = '<info>migrated</info>'; } elseif ($executedMigrations->hasMigration($version)) { $status = '<error>migrated, not available</error>'; } else { $status = '<comment>not migrated</comment>'; } $table->addRow([ (string) $version, $status, (string) $executedAt, $executionTime !== null ? $executionTime . 's' : '', $description, ]); } $table->render(); } public function showMigrationsInfo(OutputInterface $output): void { $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $availableMigrations = $this->migrationPlanCalculator->getMigrations(); $newMigrations = $this->statusCalculator->getNewMigrations(); $executedUnavailableMigrations = $this->statusCalculator->getExecutedUnavailableMigrations(); $storage = $this->configuration->getMetadataStorageConfiguration(); $table = new Table($output); $table->setHeaders( [ [new TableCell('Configuration', ['colspan' => 3])], ] ); $dataGroup = [ 'Storage' => [ 'Type' => $storage !== null ? get_class($storage) : null, ], 'Database' => [ 'Driver' => get_class($this->connection->getDriver()), 'Name' => $this->connection->getDatabase(), ], 'Versions' => [ 'Previous' => $this->getFormattedVersionAlias('prev', $executedMigrations), 'Current' => $this->getFormattedVersionAlias('current', $executedMigrations), 'Next' => $this->getFormattedVersionAlias('next', $executedMigrations), 'Latest' => $this->getFormattedVersionAlias('latest', $executedMigrations), ], 'Migrations' => [ 'Executed' => count($executedMigrations), 'Executed Unavailable' => count($executedUnavailableMigrations) > 0 ? ('<error>' . count($executedUnavailableMigrations) . '</error>') : '0', 'Available' => count($availableMigrations), 'New' => count($newMigrations) > 0 ? ('<question>' . count($newMigrations) . '</question>') : '0', ], 'Migration Namespaces' => $this->configuration->getMigrationDirectories(), ]; if ($storage instanceof TableMetadataStorageConfiguration) { $dataGroup['Storage'] += [ 'Table Name' => $storage->getTableName(), 'Column Name' => $storage->getVersionColumnName(), ]; } $first = true; foreach ($dataGroup as $group => $dataValues) { $nsRows = []; foreach ($dataValues as $k => $v) { $nsRows[] = [ $k, $v, ]; } if (count($nsRows) <= 0) { continue; } if (! $first) { $table->addRow([new TableSeparator(['colspan' => 3])]); } $first = false; array_unshift( $nsRows[0], new TableCell('<info>' . $group . '</info>', ['rowspan' => count($dataValues)]) ); $table->addRows($nsRows); } $table->render(); } private function getFormattedVersionAlias(string $alias, ExecutedMigrationsList $executedMigrations): string { try { $version = $this->aliasResolver->resolveVersionAlias($alias); } catch (Throwable $e) { $version = null; } // No version found if ($version === null) { if ($alias === 'next') { return 'Already at latest version'; } if ($alias === 'prev') { return 'Already at first version'; } } // Before first version "virtual" version number if ((string) $version === '0') { return '<comment>0</comment>'; } // Show normal version number return sprintf( '<comment>%s </comment>', (string) $version ); } } migrations/lib/Doctrine/Migrations/Tools/Console/ConsoleInputMigratorConfigurationFactory.php 0000644 00000002247 15120025744 0027000 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\MigratorConfiguration; use Symfony\Component\Console\Input\InputInterface; class ConsoleInputMigratorConfigurationFactory implements MigratorConfigurationFactory { /** @var Configuration */ private $configuration; public function __construct(Configuration $configuration) { $this->configuration = $configuration; } public function getMigratorConfiguration(InputInterface $input): MigratorConfiguration { $timeAllQueries = $input->hasOption('query-time') ? (bool) $input->getOption('query-time') : false; $dryRun = $input->hasOption('dry-run') ? (bool) $input->getOption('dry-run') : false; $allOrNothing = $input->hasOption('all-or-nothing') ? $input->getOption('all-or-nothing') : null; $allOrNothing = (bool) ($allOrNothing ?? $this->configuration->isAllOrNothing()); return (new MigratorConfiguration()) ->setDryRun($dryRun) ->setTimeAllQueries($timeAllQueries) ->setAllOrNothing($allOrNothing); } } migrations/lib/Doctrine/Migrations/Tools/Console/ConsoleLogger.php 0000644 00000010545 15120025744 0021333 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console; use DateTime; use DateTimeInterface; use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use function get_class; use function gettype; use function is_object; use function is_scalar; use function method_exists; use function sprintf; use function strpos; use function strtr; /** * PSR-3 compliant console logger. * * @internal * * @see https://www.php-fig.org/psr/psr-3/ */ final class ConsoleLogger extends AbstractLogger { public const INFO = 'info'; public const ERROR = 'error'; /** @var OutputInterface */ private $output; /** @var array<string, int> */ private $verbosityLevelMap = [ LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_VERY_VERBOSE, ]; /** @var array<string, string> */ private $formatLevelMap = [ LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ]; /** * @param array<string, int> $verbosityLevelMap * @param array<string, string> $formatLevelMap */ public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } /** * {@inheritdoc} */ public function log($level, $message, array $context = []): void { if (! isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } $output = $this->output; // Write to the error output if necessary and available if ($this->formatLevelMap[$level] === self::ERROR) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } } // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. // We only do it for efficiency here as the message formatting is relatively expensive. if ($output->getVerbosity() < $this->verbosityLevelMap[$level]) { return; } $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } /** * Interpolates context values into the message placeholders. * * @param mixed[] $context */ private function interpolate(string $message, array $context): string { if (strpos($message, '{') === false) { return $message; } $replacements = []; foreach ($context as $key => $val) { if ($val === null || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof DateTimeInterface) { $replacements["{{$key}}"] = $val->format(DateTime::RFC3339); } elseif (is_object($val)) { $replacements["{{$key}}"] = '[object ' . get_class($val) . ']'; } else { $replacements["{{$key}}"] = '[' . gettype($val) . ']'; } if (! isset($replacements["{{$key}}"])) { continue; } $replacements["{{$key}}"] = '<comment>' . $replacements["{{$key}}"] . '</comment>'; } return strtr($message, $replacements); } } migrations/lib/Doctrine/Migrations/Tools/Console/ConsoleRunner.php 0000644 00000014133 15120025744 0021362 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console; use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; use Doctrine\Migrations\Configuration\Connection\ExistingConnection; use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager; use Doctrine\Migrations\Configuration\Migration\ConfigurationFileWithFallback; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Tools\Console\Command\CurrentCommand; use Doctrine\Migrations\Tools\Console\Command\DiffCommand; use Doctrine\Migrations\Tools\Console\Command\DoctrineCommand; use Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand; use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand; use Doctrine\Migrations\Tools\Console\Command\GenerateCommand; use Doctrine\Migrations\Tools\Console\Command\LatestCommand; use Doctrine\Migrations\Tools\Console\Command\ListCommand; use Doctrine\Migrations\Tools\Console\Command\MigrateCommand; use Doctrine\Migrations\Tools\Console\Command\RollupCommand; use Doctrine\Migrations\Tools\Console\Command\StatusCommand; use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand; use Doctrine\Migrations\Tools\Console\Command\UpToDateCommand; use Doctrine\Migrations\Tools\Console\Command\VersionCommand; use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; use PackageVersions\Versions; use RuntimeException; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; use function file_exists; use function getcwd; use function is_readable; use function sprintf; use const DIRECTORY_SEPARATOR; /** * The ConsoleRunner class is used to create the Symfony Console application for the Doctrine Migrations console. * * @internal * * @see bin/doctrine-migrations.php */ class ConsoleRunner { public static function findDependencyFactory(): ?DependencyFactory { // Support for using the Doctrine ORM convention of providing a `cli-config.php` file. $configurationDirectories = [ getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config', ]; $configurationFile = null; foreach ($configurationDirectories as $configurationDirectory) { $configurationFilePath = $configurationDirectory . DIRECTORY_SEPARATOR . 'cli-config.php'; if (! file_exists($configurationFilePath)) { continue; } $configurationFile = $configurationFilePath; break; } $dependencyFactory = null; if ($configurationFile !== null) { if (! is_readable($configurationFile)) { throw new RuntimeException(sprintf( 'Configuration file "%s" cannot be read.', $configurationFile )); } $dependencyFactory = require $configurationFile; $dependencyFactory = self::checkLegacyConfiguration($dependencyFactory, $configurationFile); } if ($dependencyFactory !== null && ! ($dependencyFactory instanceof DependencyFactory)) { throw new RuntimeException(sprintf( 'Configuration file "%s" must return an instance of "%s"', $configurationFile, DependencyFactory::class )); } return $dependencyFactory; } /** @param DoctrineCommand[] $commands */ public static function run(array $commands = [], ?DependencyFactory $dependencyFactory = null): void { $cli = static::createApplication($commands, $dependencyFactory); $cli->run(); } /** @param DoctrineCommand[] $commands */ public static function createApplication(array $commands = [], ?DependencyFactory $dependencyFactory = null): Application { $cli = new Application('Doctrine Migrations', Versions::getVersion('doctrine/migrations')); $cli->setCatchExceptions(true); self::addCommands($cli, $dependencyFactory); $cli->addCommands($commands); return $cli; } public static function addCommands(Application $cli, ?DependencyFactory $dependencyFactory = null): void { $cli->addCommands([ new CurrentCommand($dependencyFactory), new DumpSchemaCommand($dependencyFactory), new ExecuteCommand($dependencyFactory), new GenerateCommand($dependencyFactory), new LatestCommand($dependencyFactory), new MigrateCommand($dependencyFactory), new RollupCommand($dependencyFactory), new StatusCommand($dependencyFactory), new VersionCommand($dependencyFactory), new UpToDateCommand($dependencyFactory), new SyncMetadataCommand($dependencyFactory), new ListCommand($dependencyFactory), ]); if ($dependencyFactory === null || ! $dependencyFactory->hasSchemaProvider()) { return; } $cli->add(new DiffCommand($dependencyFactory)); } /** * @param mixed|HelperSet $dependencyFactory * * @return mixed|DependencyFactory */ private static function checkLegacyConfiguration($dependencyFactory, string $configurationFile) { if (! ($dependencyFactory instanceof HelperSet)) { return $dependencyFactory; } $configurations = new ConfigurationFileWithFallback(); if ($dependencyFactory->has('em') && $dependencyFactory->get('em') instanceof EntityManagerHelper) { return DependencyFactory::fromEntityManager( $configurations, new ExistingEntityManager($dependencyFactory->get('em')->getEntityManager()) ); } if ($dependencyFactory->has('db') && $dependencyFactory->get('db') instanceof ConnectionHelper) { return DependencyFactory::fromConnection( $configurations, new ExistingConnection($dependencyFactory->get('db')->getConnection()) ); } throw new RuntimeException(sprintf( 'Configuration HelperSet returned by "%s" does not have a valid "em" or the "db" helper.', $configurationFile )); } } migrations/lib/Doctrine/Migrations/Tools/Console/MigratorConfigurationFactory.php 0000644 00000000472 15120025744 0024433 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools\Console; use Doctrine\Migrations\MigratorConfiguration; use Symfony\Component\Console\Input\InputInterface; interface MigratorConfigurationFactory { public function getMigratorConfiguration(InputInterface $input): MigratorConfiguration; } migrations/lib/Doctrine/Migrations/Tools/BooleanStringFormatter.php 0000644 00000001522 15120025744 0021614 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools; use function strtolower; /** * The BooleanStringFormatter class is responsible for formatting a string boolean representation to a PHP boolean value. * It is used in the XmlConfiguration class to convert the string XML boolean value to a PHP boolean value. * * @internal * * @see Doctrine\Migrations\Configuration\XmlConfiguration */ class BooleanStringFormatter { public static function toBoolean(string $value, bool $default): bool { switch (strtolower($value)) { case 'true': return true; case '1': return true; case 'false': return false; case '0': return false; default: return $default; } } } migrations/lib/Doctrine/Migrations/Tools/BytesFormatter.php 0000644 00000001252 15120025744 0020134 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools; use function floor; use function log; use function pow; use function round; /** * The BytesFormatter class is responsible for converting a bytes integer to a more human readable string. * This class is used to format the memory used for display purposes when executing migrations. * * @internal */ final class BytesFormatter { public static function formatBytes(float $size, int $precision = 2): string { $base = log($size, 1024); $suffixes = ['', 'K', 'M', 'G', 'T']; return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(int) floor($base)]; } } migrations/lib/Doctrine/Migrations/Tools/TransactionHelper.php 0000644 00000001707 15120025744 0020614 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Tools; use Doctrine\DBAL\Connection; use PDO; /** * @internal */ final class TransactionHelper { public static function commitIfInTransaction(Connection $connection): void { if (! self::inTransaction($connection)) { return; } $connection->commit(); } public static function rollbackIfInTransaction(Connection $connection): void { if (! self::inTransaction($connection)) { return; } $connection->rollBack(); } private static function inTransaction(Connection $connection): bool { $wrappedConnection = $connection->getWrappedConnection(); /* Attempt to commit or rollback while no transaction is running results in an exception since PHP 8 + pdo_mysql combination */ return ! $wrappedConnection instanceof PDO || $wrappedConnection->inTransaction(); } } migrations/lib/Doctrine/Migrations/Version/AliasResolver.php 0000644 00000001422 15120025744 0020261 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; /** * The DefaultAliasResolver class is responsible for resolving aliases like first, current, etc. to the actual version number. * * @internal */ interface AliasResolver { /** * Returns the version number from an alias. * * Supported aliases are: * * - first: The very first version before any migrations have been run. * - current: The current version. * - prev: The version prior to the current version. * - next: The version following the current version. * - latest: The latest available version. * * If an existing version number is specified, it is returned verbatim. */ public function resolveVersionAlias(string $alias): Version; } migrations/lib/Doctrine/Migrations/Version/AlphabeticalComparator.php 0000644 00000000422 15120025744 0022106 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use function strcmp; final class AlphabeticalComparator implements Comparator { public function compare(Version $a, Version $b): int { return strcmp((string) $a, (string) $b); } } migrations/lib/Doctrine/Migrations/Version/Comparator.php 0000644 00000000234 15120025744 0017615 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; interface Comparator { public function compare(Version $a, Version $b): int; } migrations/lib/Doctrine/Migrations/Version/CurrentMigrationStatusCalculator.php 0000644 00000004006 15120025744 0024221 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigration; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use function array_filter; /** * The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current * version to another version. */ final class CurrentMigrationStatusCalculator implements MigrationStatusCalculator { /** @var MigrationPlanCalculator */ private $migrationPlanCalculator; /** @var MetadataStorage */ private $metadataStorage; public function __construct( MigrationPlanCalculator $migrationPlanCalculator, MetadataStorage $metadataStorage ) { $this->migrationPlanCalculator = $migrationPlanCalculator; $this->metadataStorage = $metadataStorage; } public function getExecutedUnavailableMigrations(): ExecutedMigrationsList { $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $availableMigration = $this->migrationPlanCalculator->getMigrations(); return new ExecutedMigrationsList(array_filter($executedMigrations->getItems(), static function (ExecutedMigration $migrationInfo) use ($availableMigration): bool { return ! $availableMigration->hasMigration($migrationInfo->getVersion()); })); } public function getNewMigrations(): AvailableMigrationsList { $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $availableMigration = $this->migrationPlanCalculator->getMigrations(); return new AvailableMigrationsList(array_filter($availableMigration->getItems(), static function (AvailableMigration $migrationInfo) use ($executedMigrations): bool { return ! $executedMigrations->hasMigration($migrationInfo->getVersion()); })); } } migrations/lib/Doctrine/Migrations/Version/DbalExecutor.php 0000644 00000025057 15120025744 0020101 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use DateTimeImmutable; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\EventDispatcher; use Doctrine\Migrations\Events; use Doctrine\Migrations\Exception\SkipMigration; use Doctrine\Migrations\Metadata\MigrationPlan; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\MigratorConfiguration; use Doctrine\Migrations\ParameterFormatter; use Doctrine\Migrations\Provider\SchemaDiffProvider; use Doctrine\Migrations\Query\Query; use Doctrine\Migrations\Tools\BytesFormatter; use Doctrine\Migrations\Tools\TransactionHelper; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Symfony\Component\Stopwatch\Stopwatch; use Throwable; use function count; use function ucfirst; /** * The DbalExecutor class is responsible for executing a single migration version. * * @internal */ final class DbalExecutor implements Executor { /** @var Connection */ private $connection; /** @var SchemaDiffProvider */ private $schemaProvider; /** @var ParameterFormatter */ private $parameterFormatter; /** @var Stopwatch */ private $stopwatch; /** @var Query[] */ private $sql = []; /** @var MetadataStorage */ private $metadataStorage; /** @var LoggerInterface */ private $logger; /** @var EventDispatcher */ private $dispatcher; public function __construct( MetadataStorage $metadataStorage, EventDispatcher $dispatcher, Connection $connection, SchemaDiffProvider $schemaProvider, LoggerInterface $logger, ParameterFormatter $parameterFormatter, Stopwatch $stopwatch ) { $this->connection = $connection; $this->schemaProvider = $schemaProvider; $this->parameterFormatter = $parameterFormatter; $this->stopwatch = $stopwatch; $this->metadataStorage = $metadataStorage; $this->logger = $logger; $this->dispatcher = $dispatcher; } /** * @return Query[] */ public function getSql(): array { return $this->sql; } public function addSql(Query $sqlQuery): void { $this->sql[] = $sqlQuery; } public function execute( MigrationPlan $plan, MigratorConfiguration $configuration ): ExecutionResult { $result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable()); $this->startMigration($plan, $configuration); try { $this->executeMigration( $plan, $result, $configuration ); $result->setSql($this->sql); } catch (SkipMigration $e) { $result->setSkipped(true); $this->migrationEnd($e, $plan, $result, $configuration); } catch (Throwable $e) { $result->setError(true, $e); $this->migrationEnd($e, $plan, $result, $configuration); throw $e; } return $result; } private function startMigration( MigrationPlan $plan, MigratorConfiguration $configuration ): void { $this->sql = []; $this->dispatcher->dispatchVersionEvent( Events::onMigrationsVersionExecuting, $plan, $configuration ); if (! $plan->getMigration()->isTransactional()) { return; } // only start transaction if in transactional mode $this->connection->beginTransaction(); } private function executeMigration( MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration ): ExecutionResult { $stopwatchEvent = $this->stopwatch->start('execute'); $migration = $plan->getMigration(); $direction = $plan->getDirection(); $result->setState(State::PRE); $fromSchema = $this->getFromSchema($configuration); $migration->{'pre' . ucfirst($direction)}($fromSchema); $this->logger->info(...$this->getMigrationHeader($plan, $migration, $direction)); $result->setState(State::EXEC); $toSchema = $this->schemaProvider->createToSchema($fromSchema); $result->setToSchema($toSchema); $migration->$direction($toSchema); foreach ($migration->getSql() as $sqlQuery) { $this->addSql($sqlQuery); } foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) { $this->addSql(new Query($sql)); } if (count($this->sql) !== 0) { if (! $configuration->isDryRun()) { $this->executeResult($configuration); } else { foreach ($this->sql as $query) { $this->outputSqlQuery($query, $configuration); } } } else { $this->logger->warning('Migration {version} was executed but did not result in any SQL statements.', [ 'version' => (string) $plan->getVersion(), ]); } $result->setState(State::POST); $migration->{'post' . ucfirst($direction)}($toSchema); $stopwatchEvent->stop(); $periods = $stopwatchEvent->getPeriods(); $lastPeriod = $periods[count($periods) - 1]; $result->setTime((float) $lastPeriod->getDuration() / 1000); $result->setMemory($lastPeriod->getMemory()); $params = [ 'version' => (string) $plan->getVersion(), 'time' => $lastPeriod->getDuration(), 'memory' => BytesFormatter::formatBytes($lastPeriod->getMemory()), 'direction' => $direction === Direction::UP ? 'migrated' : 'reverted', ]; $this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params); if (! $configuration->isDryRun()) { $this->metadataStorage->complete($result); } if ($migration->isTransactional()) { TransactionHelper::commitIfInTransaction($this->connection); } $plan->markAsExecuted($result); $result->setState(State::NONE); $this->dispatcher->dispatchVersionEvent( Events::onMigrationsVersionExecuted, $plan, $configuration ); return $result; } /** * @return mixed[] */ private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $migration, string $direction): array { $versionInfo = (string) $planItem->getVersion(); $description = $migration->getDescription(); if ($description !== '') { $versionInfo .= ' (' . $description . ')'; } $params = ['version_name' => $versionInfo]; if ($direction === Direction::UP) { return ['++ migrating {version_name}', $params]; } return ['++ reverting {version_name}', $params]; } private function migrationEnd(Throwable $e, MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration): void { $migration = $plan->getMigration(); if ($migration->isTransactional()) { //only rollback transaction if in transactional mode TransactionHelper::rollbackIfInTransaction($this->connection); } $plan->markAsExecuted($result); $this->logResult($e, $result, $plan); $this->dispatcher->dispatchVersionEvent( Events::onMigrationsVersionSkipped, $plan, $configuration ); } private function logResult(Throwable $e, ExecutionResult $result, MigrationPlan $plan): void { if ($result->isSkipped()) { $this->logger->notice( 'Migration {version} skipped during {state}. Reason: "{reason}"', [ 'version' => (string) $plan->getVersion(), 'reason' => $e->getMessage(), 'state' => $this->getExecutionStateAsString($result->getState()), ] ); } elseif ($result->hasError()) { $this->logger->error( 'Migration {version} failed during {state}. Error: "{error}"', [ 'version' => (string) $plan->getVersion(), 'error' => $e->getMessage(), 'state' => $this->getExecutionStateAsString($result->getState()), ] ); } } private function executeResult(MigratorConfiguration $configuration): void { foreach ($this->sql as $key => $query) { $this->outputSqlQuery($query, $configuration); $stopwatchEvent = $this->stopwatch->start('query'); // executeQuery() must be used here because $query might return a result set, for instance REPAIR does $this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes()); $stopwatchEvent->stop(); if (! $configuration->getTimeAllQueries()) { continue; } $this->logger->notice('Query took {duration}ms', [ 'duration' => $stopwatchEvent->getDuration(), ]); } } private function outputSqlQuery(Query $query, MigratorConfiguration $configuration): void { $params = $this->parameterFormatter->formatParameters( $query->getParameters(), $query->getTypes() ); $this->logger->log( $configuration->getTimeAllQueries() ? LogLevel::NOTICE : LogLevel::DEBUG, '{query} {params}', [ 'query' => $query->getStatement(), 'params' => $params, ] ); } private function getFromSchema(MigratorConfiguration $configuration): Schema { // if we're in a dry run, use the from Schema instead of reading the schema from the database if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) { return $configuration->getFromSchema(); } return $this->schemaProvider->createFromSchema(); } private function getExecutionStateAsString(int $state): string { switch ($state) { case State::PRE: return 'Pre-Checks'; case State::POST: return 'Post-Checks'; case State::EXEC: return 'Execution'; default: return 'No State'; } } } migrations/lib/Doctrine/Migrations/Version/DbalMigrationFactory.php 0000644 00000001542 15120025744 0021555 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\DBAL\Connection; use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; /** * The DbalMigrationFactory class is responsible for creating instances of a migration class name for a DBAL connection. * * @internal */ final class DbalMigrationFactory implements MigrationFactory { /** @var Connection */ private $connection; /** @var LoggerInterface */ private $logger; public function __construct(Connection $connection, LoggerInterface $logger) { $this->connection = $connection; $this->logger = $logger; } public function createVersion(string $migrationClassName): AbstractMigration { return new $migrationClassName( $this->connection, $this->logger ); } } migrations/lib/Doctrine/Migrations/Version/DefaultAliasResolver.php 0000644 00000010612 15120025744 0021567 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria; use Doctrine\Migrations\Exception\NoMigrationsToExecute; use Doctrine\Migrations\Exception\UnknownMigrationVersion; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use function substr; /** * The DefaultAliasResolver class is responsible for resolving aliases like first, current, etc. to the actual version number. * * @internal */ final class DefaultAliasResolver implements AliasResolver { private const ALIAS_FIRST = 'first'; private const ALIAS_CURRENT = 'current'; private const ALIAS_PREV = 'prev'; private const ALIAS_NEXT = 'next'; private const ALIAS_LATEST = 'latest'; /** @var MigrationPlanCalculator */ private $migrationPlanCalculator; /** @var MetadataStorage */ private $metadataStorage; /** @var MigrationStatusCalculator */ private $migrationStatusCalculator; public function __construct( MigrationPlanCalculator $migrationPlanCalculator, MetadataStorage $metadataStorage, MigrationStatusCalculator $migrationStatusCalculator ) { $this->migrationPlanCalculator = $migrationPlanCalculator; $this->metadataStorage = $metadataStorage; $this->migrationStatusCalculator = $migrationStatusCalculator; } /** * Returns the version number from an alias. * * Supported aliases are: * * - first: The very first version before any migrations have been run. * - current: The current version. * - prev: The version prior to the current version. * - next: The version following the current version. * - latest: The latest available version. * * If an existing version number is specified, it is returned verbatimly. * * @throws NoMigrationsToExecute * @throws UnknownMigrationVersion * @throws NoMigrationsFoundWithCriteria */ public function resolveVersionAlias(string $alias): Version { $availableMigrations = $this->migrationPlanCalculator->getMigrations(); $executedMigrations = $this->metadataStorage->getExecutedMigrations(); switch ($alias) { case self::ALIAS_FIRST: case '0': return new Version('0'); case self::ALIAS_CURRENT: try { return $executedMigrations->getLast()->getVersion(); } catch (NoMigrationsFoundWithCriteria $e) { return new Version('0'); } // no break because of return case self::ALIAS_PREV: try { return $executedMigrations->getLast(-1)->getVersion(); } catch (NoMigrationsFoundWithCriteria $e) { return new Version('0'); } // no break because of return case self::ALIAS_NEXT: $newMigrations = $this->migrationStatusCalculator->getNewMigrations(); try { return $newMigrations->getFirst()->getVersion(); } catch (NoMigrationsFoundWithCriteria $e) { throw NoMigrationsToExecute::new($e); } // no break because of return case self::ALIAS_LATEST: try { return $availableMigrations->getLast()->getVersion(); } catch (NoMigrationsFoundWithCriteria $e) { return $this->resolveVersionAlias(self::ALIAS_CURRENT); } // no break because of return default: if ($availableMigrations->hasMigration(new Version($alias))) { return $availableMigrations->getMigration(new Version($alias))->getVersion(); } if (substr($alias, 0, 7) === self::ALIAS_CURRENT) { $val = (int) substr($alias, 7); $targetMigration = null; if ($val > 0) { $newMigrations = $this->migrationStatusCalculator->getNewMigrations(); return $newMigrations->getFirst($val - 1)->getVersion(); } return $executedMigrations->getLast($val)->getVersion(); } } throw UnknownMigrationVersion::new($alias); } } migrations/lib/Doctrine/Migrations/Version/Direction.php 0000644 00000000575 15120025744 0017436 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; /** * The Direction class contains constants for the directions a migration can be executed. * * @internal */ final class Direction { public const UP = 'up'; public const DOWN = 'down'; /** * This class cannot be instantiated. */ private function __construct() { } } migrations/lib/Doctrine/Migrations/Version/ExecutionResult.php 0000644 00000006414 15120025744 0020656 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use DateTimeImmutable; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Query\Query; use RuntimeException; use Throwable; use function count; /** * The ExecutionResult class is responsible for storing the result of a migration version after it executes. * * @internal */ final class ExecutionResult { /** @var Query[] */ private $sql = []; /** * Seconds * * @var float|null */ private $time; /** @var float|null */ private $memory; /** @var bool */ private $skipped = false; /** @var bool */ private $error = false; /** @var Throwable|null */ private $exception; /** @var DateTimeImmutable|null */ private $executedAt; /** @var int */ private $state; /** @var Schema|null */ private $toSchema; /** @var Version */ private $version; /** @var string */ private $direction; public function __construct(Version $version, string $direction = Direction::UP, ?DateTimeImmutable $executedAt = null) { $this->executedAt = $executedAt; $this->version = $version; $this->direction = $direction; } public function getDirection(): string { return $this->direction; } public function getExecutedAt(): ?DateTimeImmutable { return $this->executedAt; } public function setExecutedAt(DateTimeImmutable $executedAt): void { $this->executedAt = $executedAt; } public function getVersion(): Version { return $this->version; } public function hasSql(): bool { return count($this->sql) !== 0; } /** * @return Query[] */ public function getSql(): array { return $this->sql; } /** * @param Query[] $sql */ public function setSql(array $sql): void { $this->sql = $sql; } public function getTime(): ?float { return $this->time; } public function setTime(float $time): void { $this->time = $time; } public function getMemory(): ?float { return $this->memory; } public function setMemory(float $memory): void { $this->memory = $memory; } public function setSkipped(bool $skipped): void { $this->skipped = $skipped; } public function isSkipped(): bool { return $this->skipped; } public function setError(bool $error, ?Throwable $exception = null): void { $this->error = $error; $this->exception = $exception; } public function hasError(): bool { return $this->error; } public function getException(): ?Throwable { return $this->exception; } public function setToSchema(Schema $toSchema): void { $this->toSchema = $toSchema; } public function getToSchema(): Schema { if ($this->toSchema === null) { throw new RuntimeException('Cannot call getToSchema() when toSchema is null.'); } return $this->toSchema; } public function getState(): int { return $this->state; } public function setState(int $state): void { $this->state = $state; } } migrations/lib/Doctrine/Migrations/Version/Executor.php 0000644 00000001004 15120025744 0017300 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Metadata\MigrationPlan; use Doctrine\Migrations\MigratorConfiguration; use Doctrine\Migrations\Query\Query; /** * The Executor defines the interface used for adding sql for a migration and executing that sql. * * @internal */ interface Executor { public function addSql(Query $sqlQuery): void; public function execute(MigrationPlan $plan, MigratorConfiguration $migratorConfiguration): ExecutionResult; } migrations/lib/Doctrine/Migrations/Version/MigrationFactory.php 0000644 00000000511 15120025744 0020765 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\AbstractMigration; /** * The MigrationFactory is responsible for creating instances of the migration class name. */ interface MigrationFactory { public function createVersion(string $migrationClassName): AbstractMigration; } migrations/lib/Doctrine/Migrations/Version/MigrationPlanCalculator.php 0000644 00000001301 15120025744 0022260 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\MigrationPlanList; /** * The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current * version to another version. */ interface MigrationPlanCalculator { /** * @param Version[] $versions */ public function getPlanForVersions(array $versions, string $direction): MigrationPlanList; public function getPlanUntilVersion(Version $to): MigrationPlanList; /** * Returns a sorted list of migrations. */ public function getMigrations(): AvailableMigrationsList; } migrations/lib/Doctrine/Migrations/Version/MigrationStatusCalculator.php 0000644 00000000775 15120025744 0022667 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; /** * The MigrationStatusCalculator is responsible for calculating the current status of * migrated and not available versions. */ interface MigrationStatusCalculator { public function getExecutedUnavailableMigrations(): ExecutedMigrationsList; public function getNewMigrations(): AvailableMigrationsList; } migrations/lib/Doctrine/Migrations/Version/SortedMigrationPlanCalculator.php 0000644 00000013363 15120025744 0023454 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; use Doctrine\Migrations\Exception\MigrationClassNotFound; use Doctrine\Migrations\Metadata; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsList; use Doctrine\Migrations\Metadata\ExecutedMigrationsList; use Doctrine\Migrations\Metadata\MigrationPlan; use Doctrine\Migrations\Metadata\MigrationPlanList; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\MigrationsRepository; use function array_diff; use function array_filter; use function array_map; use function array_reverse; use function count; use function in_array; use function reset; use function uasort; /** * The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current * version to another version. * * @internal */ final class SortedMigrationPlanCalculator implements MigrationPlanCalculator { /** @var MigrationsRepository */ private $migrationRepository; /** @var MetadataStorage */ private $metadataStorage; /** @var Comparator */ private $sorter; public function __construct( MigrationsRepository $migrationRepository, MetadataStorage $metadataStorage, Comparator $sorter ) { $this->migrationRepository = $migrationRepository; $this->metadataStorage = $metadataStorage; $this->sorter = $sorter; } /** * @param Version[] $versions */ public function getPlanForVersions(array $versions, string $direction): MigrationPlanList { $migrationsToCheck = $this->arrangeMigrationsForDirection($direction, $this->getMigrations()); $availableMigrations = array_filter($migrationsToCheck, static function (AvailableMigration $availableMigration) use ($versions): bool { // in_array third parameter is intentionally false to force object to string casting return in_array($availableMigration->getVersion(), $versions, false); }); $planItems = array_map(static function (AvailableMigration $availableMigration) use ($direction): MigrationPlan { return new MigrationPlan($availableMigration->getVersion(), $availableMigration->getMigration(), $direction); }, $availableMigrations); if (count($planItems) !== count($versions)) { $plannedVersions = array_map(static function (MigrationPlan $migrationPlan): Version { return $migrationPlan->getVersion(); }, $planItems); $diff = array_diff($versions, $plannedVersions); throw MigrationClassNotFound::new((string) reset($diff)); } return new MigrationPlanList($planItems, $direction); } public function getPlanUntilVersion(Version $to): MigrationPlanList { if ((string) $to !== '0' && ! $this->migrationRepository->hasMigration((string) $to)) { throw MigrationClassNotFound::new((string) $to); } $availableMigrations = $this->getMigrations(); $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $direction = $this->findDirection($to, $executedMigrations); $migrationsToCheck = $this->arrangeMigrationsForDirection($direction, $availableMigrations); $toExecute = $this->findMigrationsToExecute($to, $migrationsToCheck, $direction, $executedMigrations); return new MigrationPlanList(array_map(static function (AvailableMigration $migration) use ($direction): MigrationPlan { return new MigrationPlan($migration->getVersion(), $migration->getMigration(), $direction); }, $toExecute), $direction); } public function getMigrations(): AvailableMigrationsList { $availableMigrations = $this->migrationRepository->getMigrations()->getItems(); uasort($availableMigrations, function (AvailableMigration $a, AvailableMigration $b): int { return $this->sorter->compare($a->getVersion(), $b->getVersion()); }); return new AvailableMigrationsList($availableMigrations); } private function findDirection(Version $to, ExecutedMigrationsList $executedMigrations): string { if ((string) $to === '0' || ($executedMigrations->hasMigration($to) && ! $executedMigrations->getLast()->getVersion()->equals($to))) { return Direction::DOWN; } return Direction::UP; } /** * @return AvailableMigration[] */ private function arrangeMigrationsForDirection(string $direction, Metadata\AvailableMigrationsList $availableMigrations): array { return $direction === Direction::UP ? $availableMigrations->getItems() : array_reverse($availableMigrations->getItems()); } /** * @param AvailableMigration[] $migrationsToCheck * * @return AvailableMigration[] */ private function findMigrationsToExecute(Version $to, array $migrationsToCheck, string $direction, ExecutedMigrationsList $executedMigrations): array { $toExecute = []; foreach ($migrationsToCheck as $availableMigration) { if ($direction === Direction::DOWN && $availableMigration->getVersion()->equals($to)) { break; } if ($direction === Direction::UP && ! $executedMigrations->hasMigration($availableMigration->getVersion())) { $toExecute[] = $availableMigration; } elseif ($direction === Direction::DOWN && $executedMigrations->hasMigration($availableMigration->getVersion())) { $toExecute[] = $availableMigration; } if ($direction === Direction::UP && $availableMigration->getVersion()->equals($to)) { break; } } return $toExecute; } } migrations/lib/Doctrine/Migrations/Version/State.php 0000644 00000001051 15120025744 0016564 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; /** * The State class contains constants for the different states a migration can be in during execution. * * @internal */ final class State { public const NONE = 0; public const PRE = 1; public const EXEC = 2; public const POST = 3; public const STATES = [ self::NONE, self::PRE, self::EXEC, self::POST, ]; /** * This class cannot be instantiated. */ private function __construct() { } } migrations/lib/Doctrine/Migrations/Version/Version.php 0000644 00000000766 15120025744 0017145 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations\Version; final class Version { /** @var string */ private $version; public function __construct(string $version) { $this->version = $version; } public function __toString(): string { return $this->version; } /** * @param mixed $object */ public function equals($object): bool { return $object instanceof self && $object->version === $this->version; } } migrations/lib/Doctrine/Migrations/AbstractMigration.php 0000644 00000010234 15120025744 0017477 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\Exception\AbortMigration; use Doctrine\Migrations\Exception\IrreversibleMigration; use Doctrine\Migrations\Exception\MigrationException; use Doctrine\Migrations\Exception\SkipMigration; use Doctrine\Migrations\Query\Query; use Psr\Log\LoggerInterface; use function sprintf; /** * The AbstractMigration class is for end users to extend from when creating migrations. Extend this class * and implement the required up() and down() methods. */ abstract class AbstractMigration { /** @var Connection */ protected $connection; /** @var AbstractSchemaManager */ protected $sm; /** @var AbstractPlatform */ protected $platform; /** @var LoggerInterface */ private $logger; /** @var Query[] */ private $plannedSql = []; public function __construct(Connection $connection, LoggerInterface $logger) { $this->connection = $connection; $this->sm = $this->connection->getSchemaManager(); $this->platform = $this->connection->getDatabasePlatform(); $this->logger = $logger; } /** * Indicates the transactional mode of this migration. * * If this function returns true (default) the migration will be executed * in one transaction, otherwise non-transactional state will be used to * execute each of the migration SQLs. * * Extending class should override this function to alter the return value. */ public function isTransactional(): bool { return true; } public function getDescription(): string { return ''; } public function warnIf(bool $condition, string $message = 'Unknown Reason'): void { if (! $condition) { return; } $this->logger->warning($message, ['migration' => $this]); } /** * @throws AbortMigration */ public function abortIf(bool $condition, string $message = 'Unknown Reason'): void { if ($condition) { throw new AbortMigration($message); } } /** * @throws SkipMigration */ public function skipIf(bool $condition, string $message = 'Unknown Reason'): void { if ($condition) { throw new SkipMigration($message); } } /** * @throws MigrationException|DBALException */ public function preUp(Schema $schema): void { } /** * @throws MigrationException|DBALException */ public function postUp(Schema $schema): void { } /** * @throws MigrationException|DBALException */ public function preDown(Schema $schema): void { } /** * @throws MigrationException|DBALException */ public function postDown(Schema $schema): void { } /** * @throws MigrationException|DBALException */ abstract public function up(Schema $schema): void; /** * @throws MigrationException|DBALException */ public function down(Schema $schema): void { $this->abortIf(true, sprintf('No down() migration implemented for "%s"', static::class)); } /** * @param mixed[] $params * @param mixed[] $types */ protected function addSql( string $sql, array $params = [], array $types = [] ): void { $this->plannedSql[] = new Query($sql, $params, $types); } /** * @return Query[] */ public function getSql(): array { return $this->plannedSql; } protected function write(string $message): void { $this->logger->notice($message, ['migration' => $this]); } /** * @throws IrreversibleMigration */ protected function throwIrreversibleMigrationException(?string $message = null): void { if ($message === null) { $message = 'This migration is irreversible and cannot be reverted.'; } throw new IrreversibleMigration($message); } } migrations/lib/Doctrine/Migrations/DbalMigrator.php 0000644 00000012057 15120025744 0016436 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Exception\MigrationConfigurationConflict; use Doctrine\Migrations\Metadata\MigrationPlanList; use Doctrine\Migrations\Query\Query; use Doctrine\Migrations\Tools\BytesFormatter; use Doctrine\Migrations\Tools\TransactionHelper; use Doctrine\Migrations\Version\Executor; use Psr\Log\LoggerInterface; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\StopwatchEvent; use Throwable; use function count; use const COUNT_RECURSIVE; /** * The DbalMigrator class is responsible for generating and executing the SQL for a migration. * * @internal */ class DbalMigrator implements Migrator { /** @var Stopwatch */ private $stopwatch; /** @var LoggerInterface */ private $logger; /** @var Executor */ private $executor; /** @var Connection */ private $connection; /** @var EventDispatcher */ private $dispatcher; public function __construct( Connection $connection, EventDispatcher $dispatcher, Executor $executor, LoggerInterface $logger, Stopwatch $stopwatch ) { $this->stopwatch = $stopwatch; $this->logger = $logger; $this->executor = $executor; $this->connection = $connection; $this->dispatcher = $dispatcher; } /** * @return array<string, Query[]> */ private function executeMigrations( MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration ): array { $allOrNothing = $migratorConfiguration->isAllOrNothing(); if ($allOrNothing) { $this->assertAllMigrationsAreTransactional($migrationsPlan); $this->connection->beginTransaction(); } try { $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrating, $migrationsPlan, $migratorConfiguration); $sql = $this->executePlan($migrationsPlan, $migratorConfiguration); $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrated, $migrationsPlan, $migratorConfiguration); } catch (Throwable $e) { if ($allOrNothing) { TransactionHelper::rollbackIfInTransaction($this->connection); } throw $e; } if ($allOrNothing) { TransactionHelper::commitIfInTransaction($this->connection); } return $sql; } private function assertAllMigrationsAreTransactional(MigrationPlanList $migrationsPlan): void { foreach ($migrationsPlan->getItems() as $plan) { if (! $plan->getMigration()->isTransactional()) { throw MigrationConfigurationConflict::migrationIsNotTransactional($plan->getMigration()); } } } /** * @return array<string, Query[]> */ private function executePlan(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array { $sql = []; $time = 0; foreach ($migrationsPlan->getItems() as $plan) { $versionExecutionResult = $this->executor->execute($plan, $migratorConfiguration); // capture the to Schema for the migration so we have the ability to use // it as the from Schema for the next migration when we are running a dry run // $toSchema may be null in the case of skipped migrations if (! $versionExecutionResult->isSkipped()) { $migratorConfiguration->setFromSchema($versionExecutionResult->getToSchema()); } $sql[(string) $plan->getVersion()] = $versionExecutionResult->getSql(); $time += $versionExecutionResult->getTime(); } return $sql; } /** * @param array<string, Query[]> $sql */ private function endMigrations( StopwatchEvent $stopwatchEvent, MigrationPlanList $migrationsPlan, array $sql ): void { $stopwatchEvent->stop(); $this->logger->notice( 'finished in {duration}ms, used {memory} memory, {migrations_count} migrations executed, {queries_count} sql queries', [ 'duration' => $stopwatchEvent->getDuration(), 'memory' => BytesFormatter::formatBytes($stopwatchEvent->getMemory()), 'migrations_count' => count($migrationsPlan), 'queries_count' => count($sql, COUNT_RECURSIVE) - count($sql), ] ); } /** * {@inheritDoc} */ public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array { if (count($migrationsPlan) === 0) { $this->logger->notice('No migrations to execute.'); return []; } $stopwatchEvent = $this->stopwatch->start('migrate'); $sql = $this->executeMigrations($migrationsPlan, $migratorConfiguration); $this->endMigrations($stopwatchEvent, $migrationsPlan, $sql); return $sql; } } migrations/lib/Doctrine/Migrations/DependencyFactory.php 0000644 00000041641 15120025744 0017476 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Configuration\Configuration; use Doctrine\Migrations\Configuration\Connection\ConnectionLoader; use Doctrine\Migrations\Configuration\EntityManager\EntityManagerLoader; use Doctrine\Migrations\Configuration\Migration\ConfigurationLoader; use Doctrine\Migrations\Exception\FrozenDependencies; use Doctrine\Migrations\Exception\MissingDependency; use Doctrine\Migrations\Finder\GlobFinder; use Doctrine\Migrations\Finder\MigrationFinder; use Doctrine\Migrations\Finder\RecursiveRegexFinder; use Doctrine\Migrations\Generator\ClassNameGenerator; use Doctrine\Migrations\Generator\ConcatenationFileBuilder; use Doctrine\Migrations\Generator\DiffGenerator; use Doctrine\Migrations\Generator\FileBuilder; use Doctrine\Migrations\Generator\Generator; use Doctrine\Migrations\Generator\SqlGenerator; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorage; use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration; use Doctrine\Migrations\Provider\DBALSchemaDiffProvider; use Doctrine\Migrations\Provider\EmptySchemaProvider; use Doctrine\Migrations\Provider\LazySchemaDiffProvider; use Doctrine\Migrations\Provider\OrmSchemaProvider; use Doctrine\Migrations\Provider\SchemaDiffProvider; use Doctrine\Migrations\Provider\SchemaProvider; use Doctrine\Migrations\Tools\Console\ConsoleInputMigratorConfigurationFactory; use Doctrine\Migrations\Tools\Console\Helper\MigrationStatusInfosHelper; use Doctrine\Migrations\Tools\Console\MigratorConfigurationFactory; use Doctrine\Migrations\Version\AliasResolver; use Doctrine\Migrations\Version\AlphabeticalComparator; use Doctrine\Migrations\Version\Comparator; use Doctrine\Migrations\Version\CurrentMigrationStatusCalculator; use Doctrine\Migrations\Version\DbalExecutor; use Doctrine\Migrations\Version\DbalMigrationFactory; use Doctrine\Migrations\Version\DefaultAliasResolver; use Doctrine\Migrations\Version\Executor; use Doctrine\Migrations\Version\MigrationFactory; use Doctrine\Migrations\Version\MigrationPlanCalculator; use Doctrine\Migrations\Version\MigrationStatusCalculator; use Doctrine\Migrations\Version\SortedMigrationPlanCalculator; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Symfony\Component\Stopwatch\Stopwatch; use function array_key_exists; use function call_user_func; use function preg_quote; use function sprintf; /** * The DependencyFactory is responsible for wiring up and managing internal class dependencies. */ class DependencyFactory { /** @psalm-var array<string, bool> */ private $inResolution = []; /** @var Configuration */ private $configuration; /** @var object[]|callable[] */ private $dependencies = []; /** @var Connection */ private $connection; /** @var EntityManagerInterface|null */ private $em; /** @var bool */ private $frozen = false; /** @var ConfigurationLoader */ private $configurationLoader; /** @var ConnectionLoader */ private $connectionLoader; /** @var EntityManagerLoader|null */ private $emLoader; /** @var callable[] */ private $factories = []; public static function fromConnection( ConfigurationLoader $configurationLoader, ConnectionLoader $connectionLoader, ?LoggerInterface $logger = null ): self { $dependencyFactory = new self($logger); $dependencyFactory->configurationLoader = $configurationLoader; $dependencyFactory->connectionLoader = $connectionLoader; return $dependencyFactory; } public static function fromEntityManager( ConfigurationLoader $configurationLoader, EntityManagerLoader $emLoader, ?LoggerInterface $logger = null ): self { $dependencyFactory = new self($logger); $dependencyFactory->configurationLoader = $configurationLoader; $dependencyFactory->emLoader = $emLoader; return $dependencyFactory; } private function __construct(?LoggerInterface $logger) { if ($logger === null) { return; } $this->setDefinition(LoggerInterface::class, static function () use ($logger): LoggerInterface { return $logger; }); } public function isFrozen(): bool { return $this->frozen; } public function freeze(): void { $this->frozen = true; } private function assertNotFrozen(): void { if ($this->frozen) { throw FrozenDependencies::new(); } } public function hasEntityManager(): bool { return $this->emLoader !== null; } public function setConfigurationLoader(ConfigurationLoader $configurationLoader): void { $this->assertNotFrozen(); $this->configurationLoader = $configurationLoader; } public function getConfiguration(): Configuration { if ($this->configuration === null) { $this->configuration = $this->configurationLoader->getConfiguration(); $this->freeze(); } return $this->configuration; } public function getConnection(): Connection { if ($this->connection === null) { $this->connection = $this->hasEntityManager() ? $this->getEntityManager()->getConnection() : $this->connectionLoader->getConnection($this->getConfiguration()->getConnectionName()); $this->freeze(); } return $this->connection; } public function getEntityManager(): EntityManagerInterface { if ($this->em === null) { if ($this->emLoader === null) { throw MissingDependency::noEntityManager(); } $this->em = $this->emLoader->getEntityManager($this->getConfiguration()->getEntityManagerName()); $this->freeze(); } return $this->em; } public function getVersionComparator(): Comparator { return $this->getDependency(Comparator::class, static function (): AlphabeticalComparator { return new AlphabeticalComparator(); }); } public function getLogger(): LoggerInterface { return $this->getDependency(LoggerInterface::class, static function (): LoggerInterface { return new NullLogger(); }); } public function getEventDispatcher(): EventDispatcher { return $this->getDependency(EventDispatcher::class, function (): EventDispatcher { return new EventDispatcher( $this->getConnection(), $this->getConnection()->getEventManager() ); }); } public function getClassNameGenerator(): ClassNameGenerator { return $this->getDependency(ClassNameGenerator::class, static function (): ClassNameGenerator { return new ClassNameGenerator(); }); } public function getSchemaDumper(): SchemaDumper { return $this->getDependency(SchemaDumper::class, function (): SchemaDumper { $excludedTables = []; $metadataConfig = $this->getConfiguration()->getMetadataStorageConfiguration(); if ($metadataConfig instanceof TableMetadataStorageConfiguration) { $excludedTables[] = sprintf('/^%s$/', preg_quote($metadataConfig->getTableName(), '/')); } return new SchemaDumper( $this->getConnection()->getDatabasePlatform(), $this->getConnection()->getSchemaManager(), $this->getMigrationGenerator(), $this->getMigrationSqlGenerator(), $excludedTables ); }); } private function getEmptySchemaProvider(): SchemaProvider { return $this->getDependency(EmptySchemaProvider::class, function (): SchemaProvider { return new EmptySchemaProvider( $this->getConnection()->getSchemaManager() ); }); } public function hasSchemaProvider(): bool { try { $this->getSchemaProvider(); } catch (MissingDependency $exception) { return false; } return true; } public function getSchemaProvider(): SchemaProvider { return $this->getDependency(SchemaProvider::class, function (): SchemaProvider { if ($this->hasEntityManager()) { return new OrmSchemaProvider($this->getEntityManager()); } throw MissingDependency::noSchemaProvider(); }); } public function getDiffGenerator(): DiffGenerator { return $this->getDependency(DiffGenerator::class, function (): DiffGenerator { return new DiffGenerator( $this->getConnection()->getConfiguration(), $this->getConnection()->getSchemaManager(), $this->getSchemaProvider(), $this->getConnection()->getDatabasePlatform(), $this->getMigrationGenerator(), $this->getMigrationSqlGenerator(), $this->getEmptySchemaProvider() ); }); } public function getSchemaDiffProvider(): SchemaDiffProvider { return $this->getDependency(SchemaDiffProvider::class, function (): LazySchemaDiffProvider { return LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration( new DBALSchemaDiffProvider( $this->getConnection()->getSchemaManager(), $this->getConnection()->getDatabasePlatform() ) ); }); } private function getFileBuilder(): FileBuilder { return $this->getDependency(FileBuilder::class, static function (): FileBuilder { return new ConcatenationFileBuilder(); }); } private function getParameterFormatter(): ParameterFormatter { return $this->getDependency(ParameterFormatter::class, function (): ParameterFormatter { return new InlineParameterFormatter($this->getConnection()); }); } public function getMigrationsFinder(): MigrationFinder { return $this->getDependency(MigrationFinder::class, function (): MigrationFinder { $configs = $this->getConfiguration(); $needsRecursiveFinder = $configs->areMigrationsOrganizedByYear() || $configs->areMigrationsOrganizedByYearAndMonth(); return $needsRecursiveFinder ? new RecursiveRegexFinder() : new GlobFinder(); }); } public function getMigrationRepository(): MigrationsRepository { return $this->getDependency(MigrationsRepository::class, function (): MigrationsRepository { return new FilesystemMigrationsRepository( $this->getConfiguration()->getMigrationClasses(), $this->getConfiguration()->getMigrationDirectories(), $this->getMigrationsFinder(), $this->getMigrationFactory() ); }); } public function getMigrationFactory(): MigrationFactory { return $this->getDependency(MigrationFactory::class, function (): MigrationFactory { return new DbalMigrationFactory($this->getConnection(), $this->getLogger()); }); } /** * @param object|callable $service */ public function setService(string $id, $service): void { $this->assertNotFrozen(); $this->dependencies[$id] = $service; } public function getMetadataStorage(): MetadataStorage { return $this->getDependency(MetadataStorage::class, function (): MetadataStorage { return new TableMetadataStorage( $this->getConnection(), $this->getVersionComparator(), $this->getConfiguration()->getMetadataStorageConfiguration(), $this->getMigrationRepository() ); }); } private function getVersionExecutor(): Executor { return $this->getDependency(Executor::class, function (): Executor { return new DbalExecutor( $this->getMetadataStorage(), $this->getEventDispatcher(), $this->getConnection(), $this->getSchemaDiffProvider(), $this->getLogger(), $this->getParameterFormatter(), $this->getStopwatch() ); }); } public function getQueryWriter(): QueryWriter { return $this->getDependency(QueryWriter::class, function (): QueryWriter { return new FileQueryWriter( $this->getFileBuilder(), $this->getLogger() ); }); } public function getVersionAliasResolver(): AliasResolver { return $this->getDependency(AliasResolver::class, function (): AliasResolver { return new DefaultAliasResolver( $this->getMigrationPlanCalculator(), $this->getMetadataStorage(), $this->getMigrationStatusCalculator() ); }); } public function getMigrationStatusCalculator(): MigrationStatusCalculator { return $this->getDependency(MigrationStatusCalculator::class, function (): MigrationStatusCalculator { return new CurrentMigrationStatusCalculator( $this->getMigrationPlanCalculator(), $this->getMetadataStorage() ); }); } public function getMigrationPlanCalculator(): MigrationPlanCalculator { return $this->getDependency(MigrationPlanCalculator::class, function (): MigrationPlanCalculator { return new SortedMigrationPlanCalculator( $this->getMigrationRepository(), $this->getMetadataStorage(), $this->getVersionComparator() ); }); } public function getMigrationGenerator(): Generator { return $this->getDependency(Generator::class, function (): Generator { return new Generator($this->getConfiguration()); }); } public function getMigrationSqlGenerator(): SqlGenerator { return $this->getDependency(SqlGenerator::class, function (): SqlGenerator { return new SqlGenerator( $this->getConfiguration(), $this->getConnection()->getDatabasePlatform() ); }); } public function getConsoleInputMigratorConfigurationFactory(): MigratorConfigurationFactory { return $this->getDependency(MigratorConfigurationFactory::class, function (): MigratorConfigurationFactory { return new ConsoleInputMigratorConfigurationFactory( $this->getConfiguration() ); }); } public function getMigrationStatusInfosHelper(): MigrationStatusInfosHelper { return $this->getDependency(MigrationStatusInfosHelper::class, function (): MigrationStatusInfosHelper { return new MigrationStatusInfosHelper( $this->getConfiguration(), $this->getConnection(), $this->getVersionAliasResolver(), $this->getMigrationPlanCalculator(), $this->getMigrationStatusCalculator(), $this->getMetadataStorage() ); }); } public function getMigrator(): Migrator { return $this->getDependency(Migrator::class, function (): Migrator { return new DbalMigrator( $this->getConnection(), $this->getEventDispatcher(), $this->getVersionExecutor(), $this->getLogger(), $this->getStopwatch() ); }); } public function getStopwatch(): Stopwatch { return $this->getDependency(Stopwatch::class, static function (): Stopwatch { return new Stopwatch(true); }); } public function getRollup(): Rollup { return $this->getDependency(Rollup::class, function (): Rollup { return new Rollup( $this->getMetadataStorage(), $this->getMigrationRepository() ); }); } /** * @return mixed */ private function getDependency(string $id, callable $callback) { if (! isset($this->inResolution[$id]) && array_key_exists($id, $this->factories) && ! array_key_exists($id, $this->dependencies)) { $this->inResolution[$id] = true; $this->dependencies[$id] = call_user_func($this->factories[$id], $this); unset($this->inResolution); } if (! array_key_exists($id, $this->dependencies)) { $this->dependencies[$id] = $callback(); } return $this->dependencies[$id]; } public function setDefinition(string $id, callable $service): void { $this->assertNotFrozen(); $this->factories[$id] = $service; } } migrations/lib/Doctrine/Migrations/EventDispatcher.php 0000644 00000004331 15120025744 0017153 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Common\EventArgs; use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Event\MigrationsVersionEventArgs; use Doctrine\Migrations\Metadata\MigrationPlan; use Doctrine\Migrations\Metadata\MigrationPlanList; /** * The EventDispatcher class is responsible for dispatching events internally that a user can listen for. * * @internal */ final class EventDispatcher { /** @var EventManager */ private $eventManager; /** @var Connection */ private $connection; public function __construct(Connection $connection, EventManager $eventManager) { $this->eventManager = $eventManager; $this->connection = $connection; } public function dispatchMigrationEvent( string $eventName, MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration ): void { $event = $this->createMigrationEventArgs($migrationsPlan, $migratorConfiguration); $this->dispatchEvent($eventName, $event); } public function dispatchVersionEvent( string $eventName, MigrationPlan $plan, MigratorConfiguration $migratorConfiguration ): void { $event = $this->createMigrationsVersionEventArgs( $plan, $migratorConfiguration ); $this->dispatchEvent($eventName, $event); } private function dispatchEvent(string $eventName, ?EventArgs $args = null): void { $this->eventManager->dispatchEvent($eventName, $args); } private function createMigrationEventArgs( MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration ): MigrationsEventArgs { return new MigrationsEventArgs($this->connection, $migrationsPlan, $migratorConfiguration); } private function createMigrationsVersionEventArgs( MigrationPlan $plan, MigratorConfiguration $migratorConfiguration ): MigrationsVersionEventArgs { return new MigrationsVersionEventArgs( $this->connection, $plan, $migratorConfiguration ); } } migrations/lib/Doctrine/Migrations/Events.php 0000644 00000001063 15120025744 0015326 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; /** * The Events class contains constants for event names that a user can subscribe to. */ final class Events { public const onMigrationsMigrating = 'onMigrationsMigrating'; public const onMigrationsMigrated = 'onMigrationsMigrated'; public const onMigrationsVersionExecuting = 'onMigrationsVersionExecuting'; public const onMigrationsVersionExecuted = 'onMigrationsVersionExecuted'; public const onMigrationsVersionSkipped = 'onMigrationsVersionSkipped'; } migrations/lib/Doctrine/Migrations/FileQueryWriter.php 0000644 00000003272 15120025744 0017170 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use DateTimeImmutable; use DateTimeInterface; use Doctrine\Migrations\Generator\FileBuilder; use Doctrine\Migrations\Query\Query; use Psr\Log\LoggerInterface; use function file_put_contents; use function is_dir; use function realpath; /** * The FileQueryWriter class is responsible for writing migration SQL queries to a file on disk. * * @internal */ final class FileQueryWriter implements QueryWriter { /** @var FileBuilder */ private $migrationFileBuilder; /** @var LoggerInterface */ private $logger; public function __construct( FileBuilder $migrationFileBuilder, LoggerInterface $logger ) { $this->migrationFileBuilder = $migrationFileBuilder; $this->logger = $logger; } /** * @param array<string,Query[]> $queriesByVersion */ public function write( string $path, string $direction, array $queriesByVersion, ?DateTimeInterface $now = null ): bool { $now = $now ?? new DateTimeImmutable(); $string = $this->migrationFileBuilder ->buildMigrationFile($queriesByVersion, $direction, $now); $path = $this->buildMigrationFilePath($path, $now); $this->logger->info('Writing migration file to "{path}"', ['path' => $path]); return file_put_contents($path, $string) !== false; } private function buildMigrationFilePath(string $path, DateTimeInterface $now): string { if (is_dir($path)) { $path = realpath($path); $path .= '/doctrine_migration_' . $now->format('YmdHis') . '.sql'; } return $path; } } migrations/lib/Doctrine/Migrations/FilesystemMigrationsRepository.php 0000644 00000010343 15120025744 0022344 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Exception\DuplicateMigrationVersion; use Doctrine\Migrations\Exception\MigrationClassNotFound; use Doctrine\Migrations\Exception\MigrationException; use Doctrine\Migrations\Finder\MigrationFinder; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsSet; use Doctrine\Migrations\Version\MigrationFactory; use Doctrine\Migrations\Version\Version; use function class_exists; /** * The FilesystemMigrationsRepository class is responsible for retrieving migrations, determining what the current migration * version, etc. * * @internal */ class FilesystemMigrationsRepository implements MigrationsRepository { /** @var bool */ private $migrationsLoaded = false; /** @var array<string, string> */ private $migrationDirectories; /** @var MigrationFinder */ private $migrationFinder; /** @var MigrationFactory */ private $versionFactory; /** @var AvailableMigration[] */ private $migrations = []; /** * @param string[] $classes * @param array<string, string> $migrationDirectories */ public function __construct( array $classes, array $migrationDirectories, MigrationFinder $migrationFinder, MigrationFactory $versionFactory ) { $this->migrationDirectories = $migrationDirectories; $this->migrationFinder = $migrationFinder; $this->versionFactory = $versionFactory; $this->registerMigrations($classes); } private function registerMigrationInstance(Version $version, AbstractMigration $migration): AvailableMigration { if (isset($this->migrations[(string) $version])) { throw DuplicateMigrationVersion::new( (string) $version, (string) $version ); } $this->migrations[(string) $version] = new AvailableMigration($version, $migration); return $this->migrations[(string) $version]; } /** @throws MigrationException */ public function registerMigration(string $migrationClassName): AvailableMigration { $this->ensureMigrationClassExists($migrationClassName); $version = new Version($migrationClassName); $migration = $this->versionFactory->createVersion($migrationClassName); return $this->registerMigrationInstance($version, $migration); } /** * @param string[] $migrations * * @return AvailableMigration[] */ private function registerMigrations(array $migrations): array { $versions = []; foreach ($migrations as $class) { $versions[] = $this->registerMigration($class); } return $versions; } public function hasMigration(string $version): bool { $this->loadMigrationsFromDirectories(); return isset($this->migrations[$version]); } public function getMigration(Version $version): AvailableMigration { $this->loadMigrationsFromDirectories(); if (! isset($this->migrations[(string) $version])) { throw MigrationClassNotFound::new((string) $version); } return $this->migrations[(string) $version]; } /** * Returns a non-sorted set of migrations. */ public function getMigrations(): AvailableMigrationsSet { $this->loadMigrationsFromDirectories(); return new AvailableMigrationsSet($this->migrations); } /** @throws MigrationException */ private function ensureMigrationClassExists(string $class): void { if (! class_exists($class)) { throw MigrationClassNotFound::new($class); } } private function loadMigrationsFromDirectories(): void { $migrationDirectories = $this->migrationDirectories; if ($this->migrationsLoaded) { return; } $this->migrationsLoaded = true; foreach ($migrationDirectories as $namespace => $path) { $migrations = $this->migrationFinder->findMigrations( $path, $namespace ); $this->registerMigrations($migrations); } } } migrations/lib/Doctrine/Migrations/InlineParameterFormatter.php 0000644 00000004462 15120025744 0021033 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type; use function array_map; use function implode; use function is_array; use function is_bool; use function is_int; use function is_string; use function sprintf; /** * The InlineParameterFormatter class is responsible for formatting SQL query parameters to a string * for display output. * * @internal */ final class InlineParameterFormatter implements ParameterFormatter { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** * @param mixed[] $params * @param mixed[] $types */ public function formatParameters(array $params, array $types): string { if ($params === []) { return ''; } $formattedParameters = []; foreach ($params as $key => $value) { $type = $types[$key] ?? 'string'; $formattedParameter = '[' . $this->formatParameter($value, $type) . ']'; $formattedParameters[] = is_string($key) ? sprintf(':%s => %s', $key, $formattedParameter) : $formattedParameter; } return sprintf('with parameters (%s)', implode(', ', $formattedParameters)); } /** * @param string|int $value * @param string|int $type * * @return string|int */ private function formatParameter($value, $type) { if (is_string($type) && Type::hasType($type)) { return Type::getType($type)->convertToDatabaseValue( $value, $this->connection->getDatabasePlatform() ); } return $this->parameterToString($value); } /** * @param int[]|bool[]|string[]|array|int|string|bool $value */ private function parameterToString($value): string { if (is_array($value)) { return implode(', ', array_map(function ($value): string { return $this->parameterToString($value); }, $value)); } if (is_int($value) || is_string($value)) { return (string) $value; } if (is_bool($value)) { return $value === true ? 'true' : 'false'; } } } migrations/lib/Doctrine/Migrations/MigrationsRepository.php 0000644 00000000674 15120025744 0020305 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Metadata\AvailableMigration; use Doctrine\Migrations\Metadata\AvailableMigrationsSet; use Doctrine\Migrations\Version\Version; interface MigrationsRepository { public function hasMigration(string $version): bool; public function getMigration(Version $version): AvailableMigration; public function getMigrations(): AvailableMigrationsSet; } migrations/lib/Doctrine/Migrations/Migrator.php 0000644 00000001024 15120025744 0015643 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Metadata\MigrationPlanList; use Doctrine\Migrations\Query\Query; /** * The Migrator interface is responsible for generating and executing the SQL for a migration. * * @internal */ interface Migrator { /** * @return array<string, Query[]> A list of SQL statements executed, grouped by migration version */ public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array; } migrations/lib/Doctrine/Migrations/MigratorConfiguration.php 0000644 00000003452 15120025744 0020402 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Schema\Schema; /** * The MigratorConfiguration class is responsible for defining the configuration for a migration. * * @internal * * @see Doctrine\Migrations\DbalMigrator * @see Doctrine\Migrations\Version\DbalExecutor */ class MigratorConfiguration { /** @var bool */ private $dryRun = false; /** @var bool */ private $timeAllQueries = false; /** @var bool */ private $noMigrationException = false; /** @var bool */ private $allOrNothing = false; /** @var Schema|null */ private $fromSchema; public function isDryRun(): bool { return $this->dryRun; } public function setDryRun(bool $dryRun): self { $this->dryRun = $dryRun; return $this; } public function getTimeAllQueries(): bool { return $this->timeAllQueries; } public function setTimeAllQueries(bool $timeAllQueries): self { $this->timeAllQueries = $timeAllQueries; return $this; } public function getNoMigrationException(): bool { return $this->noMigrationException; } public function setNoMigrationException(bool $noMigrationException = false): self { $this->noMigrationException = $noMigrationException; return $this; } public function isAllOrNothing(): bool { return $this->allOrNothing; } public function setAllOrNothing(bool $allOrNothing): self { $this->allOrNothing = $allOrNothing; return $this; } public function getFromSchema(): ?Schema { return $this->fromSchema; } public function setFromSchema(Schema $fromSchema): self { $this->fromSchema = $fromSchema; return $this; } } migrations/lib/Doctrine/Migrations/ParameterFormatter.php 0000644 00000000677 15120025744 0017700 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; /** * The ParameterFormatter defines the interface for formatting SQL query parameters to a string * for display output. * * @internal * * @see Doctrine\Migrations\InlineParameterFormatter */ interface ParameterFormatter { /** * @param mixed[] $params * @param mixed[] $types */ public function formatParameters(array $params, array $types): string; } migrations/lib/Doctrine/Migrations/QueryWriter.php 0000644 00000000673 15120025744 0016372 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Query\Query; /** * The QueryWriter defines the interface used for writing migration SQL queries to a file on disk. * * @internal */ interface QueryWriter { /** * @param array<string,Query[]> $queriesByVersion */ public function write( string $path, string $direction, array $queriesByVersion ): bool; } migrations/lib/Doctrine/Migrations/Rollup.php 0000644 00000002726 15120025744 0015346 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\Migrations\Exception\RollupFailed; use Doctrine\Migrations\Metadata\Storage\MetadataStorage; use Doctrine\Migrations\Version\ExecutionResult; use Doctrine\Migrations\Version\Version; use function count; /** * The Rollup class is responsible for deleting all previously executed migrations from the versions table * and marking the freshly dumped schema migration (that was created with SchemaDumper) as migrated. * * @internal */ class Rollup { /** @var MigrationsRepository */ private $migrationRepository; /** @var MetadataStorage */ private $metadataStorage; public function __construct( MetadataStorage $metadataStorage, MigrationsRepository $migrationRepository ) { $this->migrationRepository = $migrationRepository; $this->metadataStorage = $metadataStorage; } /** * @throws RollupFailed */ public function rollup(): Version { $versions = $this->migrationRepository->getMigrations(); if (count($versions) === 0) { throw RollupFailed::noMigrationsFound(); } if (count($versions) > 1) { throw RollupFailed::tooManyMigrations(); } $this->metadataStorage->reset(); $result = new ExecutionResult($versions->getItems()[0]->getVersion()); $this->metadataStorage->complete($result); return $result->getVersion(); } } migrations/lib/Doctrine/Migrations/SchemaDumper.php 0000644 00000013212 15120025744 0016436 0 ustar 00 <?php declare(strict_types=1); namespace Doctrine\Migrations; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Table; use Doctrine\Migrations\Exception\NoTablesFound; use Doctrine\Migrations\Generator\Generator; use Doctrine\Migrations\Generator\SqlGenerator; use InvalidArgumentException; use function array_merge; use function count; use function implode; use function preg_last_error; use function preg_match; use function restore_error_handler; use function set_error_handler; use function sprintf; use const PREG_BACKTRACK_LIMIT_ERROR; use const PREG_BAD_UTF8_ERROR; use const PREG_BAD_UTF8_OFFSET_ERROR; use const PREG_INTERNAL_ERROR; use const PREG_RECURSION_LIMIT_ERROR; /** * The SchemaDumper class is responsible for dumping the current state of your database schema to a migration. This * is to be used in conjunction with the Rollup class. * * @internal * * @see Doctrine\Migrations\Rollup */ class SchemaDumper { /** @var AbstractPlatform */ private $platform; /** @var AbstractSchemaManager */ private $schemaManager; /** @var Generator */ private $migrationGenerator; /** @var SqlGenerator */ private $migrationSqlGenerator; /** @var string[] */ private $excludedTablesRegexes; /** * @param string[] $excludedTablesRegexes */ public function __construct( AbstractPlatform $platform, AbstractSchemaManager $schemaManager, Generator $migrationGenerator, SqlGenerator $migrationSqlGenerator, array $excludedTablesRegexes = [] ) { $this->platform = $platform; $this->schemaManager = $schemaManager; $this->migrationGenerator = $migrationGenerator; $this->migrationSqlGenerator = $migrationSqlGenerator; $this->excludedTablesRegexes = $excludedTablesRegexes; } /** * @param string[] $excludedTablesRegexes * * @throws NoTablesFound */ public function dump( string $fqcn, array $excludedTablesRegexes = [], bool $formatted = false, int $lineLength = 120 ): string { $schema = $this->schemaManager->createSchema(); $up = []; $down = []; foreach ($schema->getTables() as $table) { if ($this->shouldSkipTable($table, $excludedTablesRegexes)) { continue; } $upSql = $this->platform->getCreateTableSQL($table); $upCode = $this->migrationSqlGenerator->generate( $upSql, $formatted, $lineLength ); if ($upCode !== '') { $up[] = $upCode; } $downSql = [$this->platform->getDropTableSQL($table)]; $downCode = $this->migrationSqlGenerator->generate( $downSql, $formatted, $lineLength ); if ($downCode === '') { continue; } $down[] = $downCode; } if (count($up) === 0) { throw NoTablesFound::new(); } $up = implode("\n", $up); $down = implode("\n", $down); return $this->migrationGenerator->generateMigration( $fqcn, $up, $down ); } /** * @param string[] $excludedTablesRegexes */ private function shouldSkipTable(Table $table, array $excludedTablesRegexes): bool { foreach (array_merge($excludedTablesRegexes, $this->excludedTablesRegexes) as $regex) { if (self::pregMatch($regex, $table->getName()) !== 0) { return true; } } return false; } /** * A local wrapper for "preg_match" which will throw a InvalidArgumentException if there * is an internal error in the PCRE engine. * Copied from https://github.com/symfony/symfony/blob/62216ea67762b18982ca3db73c391b0748a49d49/src/Symfony/Component/Yaml/Parser.php#L1072-L1090 * * @internal * * @param mixed[] $matches */ private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int { try { $errorMessages = []; set_error_handler(static function (int $severity, string $message, string $file, int $line) use (&$errorMessages): bool { $errorMessages[] = $message; return true; }); $ret = preg_match($pattern, $subject, $matches, $flags, $offset); } finally { restore_error_handler(); } if ($ret === false) { switch (preg_last_error()) { case PREG_INTERNAL_ERROR: $error = sprintf('Internal PCRE error, please check your Regex. Reported errors: %s.', implode(', ', $errorMessages)); break; case PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Error.'; } throw new InvalidArgumentException($error); } return $ret; } } migrations/LICENSE 0000644 00000002051 15120025744 0007723 0 ustar 00 Copyright (c) 2006-2018 Doctrine Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. migrations/README.md 0000644 00000001441 15120025744 0010177 0 ustar 00 # Doctrine Migrations [](https://github.com/doctrine/migrations/actions) [](https://codecov.io/gh/doctrine/migrations/branch/3.1.x) [](https://packagist.org/packages/doctrine/migrations) [](https://packagist.org/packages/doctrine/migrations) [](LICENSE) ## Documentation All available documentation can be found [here](https://www.doctrine-project.org/projects/migrations.html). migrations/UPGRADE.md 0000644 00000050107 15120025744 0010334 0 ustar 00 # Upgrade to 3.1 - The "version" is the FQCN of the migration class (existing entries in the migrations table will be automatically updated). - `MigrationsEventArgs` and `MigrationsVersionEventArgs` expose different API, please refer to the [Code BC breaks](#code-bc-breaks) section. ## Console - Console output changed. The commands use a different output style. If you were relying on specific output, please update your scripts. Console output is not covered by the BC promise, so please try not to rely on specific a output. Different levels of verbosity are available now (`-v`, `-vv` and `-vvv` ). - The `--show-versions` option from `migrations:status` command has been removed, use `migrations:list` instead. - The `--write-sql` option for `migrations:migrate` and `migrations:execute` does not imply dry-run anymore, use the `--dry-run` parameter instead. - The `--db` option has been renamed to `--conn`. ## Migrations table - The migrations table now has a new column named `execution_time`. - Running the `migrations:migrate` or `migrations:execute` command will automatically upgrade the migration table structure; a dedicated `migrations:sync-metadata-storage` command is available to sync manually the migrations table. ## Migration template - The `<version>` placeholder has been replaced by the `<className>` placeholder. ## Configuration files *migrations.php Before* ```php <?php return [ 'name' => 'My Project Migrations', 'migrations_namespace' => 'MyProject\Migrations', 'table_name' => 'doctrine_migration_versions', 'column_name' => 'version', 'column_length' => 14, 'executed_at_column_name' => 'executed_at', 'migrations_directory' => '/data/doctrine/migrations-docs-example/lib/MyProject/Migrations', 'all_or_nothing' => true, 'check_database_platform' => true, ]; ``` *migrations.php After* ```php <?php return [ 'table_storage' => [ 'table_name' => 'doctrine_migration_versions', 'version_column_name' => 'version', 'version_column_length' => 191, 'executed_at_column_name' => 'executed_at', 'execution_time_column_name' => 'execution_time', ], 'migrations_paths' => [ 'MyProject\Migrations' => '/data/doctrine/migrations/lib/MyProject/Migrations', 'MyProject\Component\Migrations' => './Component/MyProject/Migrations', ], 'all_or_nothing' => true, 'check_database_platform' => true, ]; ``` Files in XML, YAML or JSON also changed in a similar way. Please refer to the official documentation for more details. Note: the `name` property has been removed. Note: the option in `table_storage` needs to be updated only if you have changed the metadata table settings by using v2 options such as `table_name`, `column_name`, `column_length` or `executed_at_column_name`. If you did not change those settings, it is recommended to not provide the options and let doctrine figure out the best settings. ## Code BC breaks Most of the code is protected by the `@internal` declaration and in a very rare cases you might have dealt with the internals of this library. The most important BC breaks are in the `Doctrine\Migrations\Configuration\Configuration` class and in the helper system that now has been replaced by the `Doctrine\Migrations\DependencyFactory` functionalities. Here is a list of the most important changes: - Namespace `Doctrine\Migrations\Configuration\Configuration` - CHANGED: Class `Doctrine\Migrations\Configuration\Configuration` became final - REMOVED: Constant `Doctrine\Migrations\Configuration\Configuration::VERSION_FORMAT` was removed, there is not more limitation on the version format - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#__construct()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setName()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getName()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getConnection()` was removed, use `Doctrine\Migrations\DependencyFactory#getConnection()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsTableName()` was removed, use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsTableName()` was removed, use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsColumnName()` was removed, use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsColumnName()` was removed, use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQuotedMigrationsColumnName()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsColumnLength()` was removed, use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsColumnLength()` was removed, use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsExecutedAtColumnName()` was removed, use `Doctrine\Migrations\Configuration\Configuration#setMetadataStorageConfiguration` with an instance of `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsExecutedAtColumnName()` was removed, use `Doctrine\Migrations\Metadata\Storage\MetadataStorageConfiguration#getMetadataStorageConfiguration` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQuotedMigrationsExecutedAtColumnName()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsDirectory()` was removed, use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsDirectory()` was removed, use `Doctrine\Migrations\Configuration\Configuration#getMigrationDirectories()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsNamespace()` was removed, use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsNamespace()` was removed, use `Doctrine\Migrations\Configuration\Configuration#getMigrationDirectories()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setMigrationsFinder()` was removed, use the dependency factory instead - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsFinder()` was removed, use the dependency factory instead - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#hasVersionMigrated()` was removed, use the dependency factory instead - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getVersionData()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#resolveVersionAlias()` was removed, use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#isMigrationTableCreated()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#createMigrationTable()` was removed, use `Doctrine\Migrations\Metadata\Storage\MetadataStorage#ensureInitialized()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDateTime()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#generateVersionNumber()` was removed, use `Doctrine\Migrations\Generator\ClassNameGenerator#generateClassName()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#connect()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchMigrationEvent()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchVersionEvent()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#dispatchEvent()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNumberOfExecutedMigrations()` was removed, use `Doctrine\Migrations\DependencyFactory#getMetadataStorage()->getExecutedMigrations()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNumberOfAvailableMigrations()` was removed, use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getLatestVersion()` was removed, use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigratedVersions()` was removed, use `Doctrine\Migrations\DependencyFactory#getMetadataStorage()->getExecutedMigrations()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getAvailableVersions()` was removed use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getCurrentVersion()` was removed, use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigrationsFromDirectory()` was removed, use `Doctrine\Migrations\Configuration\Configuration#addMigrationsDirectory()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigration()` was removed, use `Doctrine\Migrations\Configuration\Configuration#addMigrationClass()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#registerMigrations()` was removed use `Doctrine\Migrations\Configuration\Configuration#addMigrationClass()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrations()` was removed, use `Doctrine\Migrations\DependencyFactory#getMigrationRepository()->getMigrations()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getVersion()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getMigrationsToExecute()` was removed, use `Doctrine\Migrations\Version\MigrationPlanCalculator#getPlanUntilVersion()` to create a migration plan - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getPrevVersion()` was removed, use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getNextVersion()` was removed, use `Doctrine\Migrations\Version\AliasResolver#resolveVersionAlias()` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getRelativeVersion()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDeltaVersion()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#setOutputWriter()` was removed, set the `Psr\Log\LoggerInterface` service in `Doctrine\Migrations\DependencyFactory` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getOutputWriter()` was removed, get the `Psr\Log\LoggerInterface` service from `Doctrine\Migrations\DependencyFactory` - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getQueryWriter()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#getDependencyFactory()` was removed - REMOVED: Method `Doctrine\Migrations\Configuration\Configuration#validate()` was removed - Namespace `Doctrine\Migrations\Configuration\Connection\Loader\Exception` - REMOVED: Class `Doctrine\Migrations\Configuration\Connection\Loader\Exception\LoaderException` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Connection\Loader\Exception\InvalidConfiguration` has been deleted - Namespace `Doctrine\Migrations\Configuration\Exception` - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\ParameterIncompatibleWithFinder` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\InvalidConfigurationKey` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\MigrationsNamespaceRequired` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\XmlNotValid` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\YamlNotAvailable` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\FileAlreadyLoaded` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\JsonNotValid` has been deleted - REMOVED: Class `Doctrine\Migrations\Configuration\Exception\YamlNotValid` has been deleted - CHANGED: The number of required arguments for `Doctrine\Migrations\Configuration\Exception\FileNotFound::new()` increased from 0 to 1 - Namespace `Doctrine\Migrations\Event\MigrationsEventArgs` - CHANGED: Class `Doctrine\Migrations\Event\MigrationsEventArgs` became final - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#getConfiguration()` was removed - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#getDirection()` was removed, use `Doctrine\Migrations\Event\MigrationsEventArgs#getPlan()` - REMOVED: Method `Doctrine\Migrations\Event\MigrationsEventArgs#isDryRun()` was removed, use `Doctrine\Migrations\Event\MigrationsEventArgs#getMigratorConfiguration()` - CHANGED: `Doctrine\Migrations\Event\MigrationsEventArgs#__construct()` arguments have been updated - Namespace `Doctrine\Migrations\Event\MigrationsVersionEventArgs` - CHANGED: Class `Doctrine\Migrations\Event\MigrationsVersionEventArgs` became final - REMOVED: Method `Doctrine\Migrations\Event\MigrationsVersionEventArgs#getVersion()` was removed use `Doctrine\Migrations\Event\MigrationsEventArgs#getPlan()` - Namespace `Doctrine\Migrations\Finder` - REMOVED: These ancestors of `Doctrine\Migrations\Finder\RecursiveRegexFinder` have been removed: ["Doctrine\\Migrations\\Finder\\MigrationDeepFinder"] - REMOVED: Class `Doctrine\Migrations\Finder\MigrationDeepFinder` has been deleted - Namespace `Doctrine\Migrations\Tools\Console\Command` - CHANGED: All non abstract classes in `Doctrine\Migrations\Tools\Console\Command\*` became final - REMOVED: Class `Doctrine\Migrations\Tools\Console\Command\AbstractCommand` has been renamed into `Doctrine\Migrations\Tools\Console\Command\DoctrineCommand` and has been marked as internal - CHANGED: Method `Doctrine\Migrations\Tools\Console\Command\*Command#__construct()` changed signature into `(?Doctrine\Migrations\DependencyFactory $di, ?string $name)` - CHANGED: Method `initialize()` of Class `Doctrine\Migrations\Tools\Console\Command\AbstractCommand` visibility reduced from `public` to `protected` - CHANGED: Method `execute()` of Class `Doctrine\Migrations\Tools\Console\Command\*Command` visibility reduced from `public` to `protected` - REMOVED: Method `Doctrine\Migrations\Tools\Console\Command\DiffCommand#createMigrationDiffGenerator()` was removed - Namespace `Doctrine\Migrations\Tools\Console\Exception` - CHANGED: The number of required arguments for `Doctrine\Migrations\Tools\Console\Exception\SchemaDumpRequiresNoMigrations::new()` increased from 0 to 1 - REMOVED: Class `Doctrine\Migrations\Tools\Console\Exception\ConnectionNotSpecified` has been deleted - Namespace `Migrations\Tools\Console\Helper` - REMOVED: All classes and namespaces are marked as internal or have been removed, use `Doctrine\Migrations\DependencyFactory` instead - Namespace `Doctrine\Migrations\AbstractMigration` - CHANGED: The method `Doctrine\Migrations\AbstractMigration#__construct()` changed signature into `(Doctrine\DBAL\Connection $conn, PSR\Log\LoggerInterface $logger)` - CHANGED: The method `Doctrine\Migrations\AbstractMigration#down()` is not abstract anymore, the default implementation will abort the migration process - REMOVED: Property `Doctrine\Migrations\AbstractMigration#$version` was removed - Namespace `Doctrine\Migrations\Provider` - REMOVED: Class `Doctrine\Migrations\Provider\SchemaProviderInterface` has been deleted - REMOVED: These ancestors of `Doctrine\Migrations\Provider\StubSchemaProvider` have been removed: ["Doctrine\\Migrations\\Provider\\SchemaProviderInterface"] - Namespace `Doctrine\Migrations\Exception` - REMOVED: Class `Doctrine\Migrations\Exception\MigrationNotConvertibleToSql` has been deleted - REMOVED: Class `Doctrine\Migrations\Exception\MigrationsDirectoryRequired` has been deleted - REMOVED: Class `Doctrine\Migrations\Version\Factory` became the interface `Doctrine\Migrations\Version\MigrationFactory` - REMOVED: Class `Doctrine\Migrations\OutputWriter` has been deleted, use `Psr\Log\Loggerinterface` # Upgrade to 2.0 ## BC Break: Moved `Doctrine\DBAL\Migrations` to `Doctrine\Migrations` Your migration classes that previously used to extend `Doctrine\DBAL\Migrations\AbstractMigration` now need to extend `Doctrine\Migrations\AbstractMigration` instead. The `Doctrine\DBAL\Migrations\AbstractMigration` class will be deprecated in the `1.8.0` release to prepare for the BC break. ## BC Break: Removed `Doctrine\DBAL\Migrations\MigrationsVersion` The `Doctrine\DBAL\Migrations\MigrationsVersion` class is no longer available: please refrain from checking the Migrations version at runtime. ## BC Break: Moved `Doctrine\Migrations\Migration` to `Doctrine\Migrations\Migrator` To make the name more clear and to differentiate from the `AbstractMigration` class, `Migration` was renamed to `Migrator`. ## BC Break: Moved exception classes from `Doctrine\Migrations\%name%Exception` to `Doctrine\Migrations\Exception\%name%` doctrine/migrations#636 Follows concept introduced in ORM (doctrine/orm#6743 + doctrine/orm#7210) and naming follows pattern accepted in Doctrine CS. # Upgrade from 1.0-alpha1 to 1.0.0-alpha3 ## AbstractMigration ### Before: The method `getName()` was defined and it's implementation would change the order in which the migration would be processed. It would cause discrepancies between the file order in a file browser and the order of execution of the migrations. ### After: The `getName()` method as been removed | set final and new `getDescription()` method has been added. The goal of this method is to be able to provide context for the migration. This context is shown for the last migrated migration when the status command is called. ## --write-sql option from the migrate command ### Before: The `--write-sql` option would only output sql contained in the migration and would not update the table containing the migrated migrations. ### After: That option now also output the sql queries necessary to update the table containing the state of the migrations. If you want to go back to the previous behavior just make a request on the bug tracker as for now the need for it is not very clear. ## MigrationsVersion::VERSION ### Before: `MigrationsVersion::VERSION` used to be a property. The returned value was fanciful. ### After: It is now a a function so that a different value can be automatically send back if it's a modified version that's used. The returned value is now the git tag. The tag is in lowercase as the other doctrine projects. migrations/build-phar.sh 0000644 00000000556 15120025744 0011311 0 ustar 00 #!/usr/bin/env bash set -euf -o pipefail ./download-box.sh function restorePlatform { composer config --unset platform mv -f composer.lock.back composer.lock || true } # lock PHP to minimum allowed version composer config platform.php 7.2.0 cp composer.lock composer.lock.back || true composer update php box.phar compile -vv trap restorePlatform exit migrations/composer.json 0000644 00000005006 15120025744 0011443 0 ustar 00 { "name": "doctrine/migrations", "type": "library", "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", "keywords": [ "database", "migrations", "dbal" ], "homepage": "https://www.doctrine-project.org/projects/migrations.html", "license": "MIT", "authors": [ { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" }, { "name": "Michael Simonson", "email": "contact@mikesimonson.com" } ], "require": { "php": "^7.2 || ^8.0", "composer/package-versions-deprecated": "^1.8", "doctrine/dbal": "^2.11", "doctrine/event-manager": "^1.0", "friendsofphp/proxy-manager-lts": "^1.0", "psr/log": "^1.1.3", "symfony/console": "^3.4 || ^4.4.16 || ^5.0", "symfony/stopwatch": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { "ext-pdo_sqlite": "*", "doctrine/coding-standard": "^8.0", "doctrine/orm": "^2.6", "doctrine/persistence": "^1.3 || ^2.0", "doctrine/sql-formatter": "^1.0", "ergebnis/composer-normalize": "^2.9", "phpstan/phpstan": "^0.12", "phpstan/phpstan-deprecation-rules": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", "phpstan/phpstan-symfony": "^0.12", "phpunit/phpunit": "^8.5 || ^9.4", "symfony/cache": "^5.3", "symfony/process": "^3.4 || ^4.0 || ^5.0", "symfony/yaml": "^3.4 || ^4.0 || ^5.0" }, "suggest": { "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", "symfony/yaml": "Allows the use of yaml for migration configuration files." }, "config": { "sort-packages": true }, "extra": { "composer-normalize": { "indent-size": 4, "indent-style": "space" } }, "autoload": { "psr-4": { "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" } }, "autoload-dev": { "psr-4": { "Doctrine\\Migrations\\Tests\\": "tests/Doctrine/Migrations/Tests" } }, "bin": [ "bin/doctrine-migrations" ] } migrations/download-box.sh 0000644 00000000305 15120025744 0011647 0 ustar 00 #!/usr/bin/env bash if [ ! -f box.phar ]; then wget https://github.com/box-project/box/releases/download/$(php -r 'echo PHP_VERSION_ID >= 70300 ? "3.11.0" : "3.9.1";')/box.phar -O box.phar fi migrations/phpstan.neon.dist 0000644 00000005230 15120025744 0012220 0 ustar 00 parameters: level: 7 paths: - %currentWorkingDirectory%/lib - %currentWorkingDirectory%/tests autoload_directories: - %currentWorkingDirectory%/tests/Doctrine/Migrations/Tests/Finder/_features - %currentWorkingDirectory%/tests/Doctrine/Migrations/Tests/Finder/_files excludes_analyse: - %currentWorkingDirectory%/tests/Doctrine/Migrations/Tests/Configuration/ConfigurationTestSource/Migrations/Version123.php ignoreErrors: - '~Variable method call on Doctrine\\Migrations\\AbstractMigration~' - message: '~^Call to function in_array\(\) requires parameter #3 to be true\.$~' path: %currentWorkingDirectory%/lib/Doctrine/Migrations/Version/SortedMigrationPlanCalculator.php - message: '~^Variable property access on mixed\.$~' path: %currentWorkingDirectory%/lib/Doctrine/Migrations/Configuration/Migration/XmlFile.php - message: '~^Call to function is_bool\(\) with bool will always evaluate to true\.$~' path: %currentWorkingDirectory%/lib/Doctrine/Migrations/InlineParameterFormatter.php - message: '~^Call to an undefined method Symfony\\Component\\Console\\Output\\OutputInterface\:\:getErrorOutput\(\)\.$~' path: %currentWorkingDirectory%/lib/Doctrine/Migrations/Tools/Console/ConsoleLogger.php - message: '~^Method Doctrine\\Migrations\\Tests\\Stub\\DoctrineRegistry::getService\(\) should return Doctrine\\Persistence\\ObjectManager but returns Doctrine\\DBAL\\Connection\|Doctrine\\ORM\\EntityManager~' path: %currentWorkingDirectory%/tests/Doctrine/Migrations/Tests/Stub/DoctrineRegistry.php - '~Call to method getVersion\(\) of deprecated class PackageVersions\\Versions\:.*~' - message: '~^Instantiation of deprecated class~' paths: - tests/Doctrine/Migrations/Tests/Tools/Console/legacy-config-dbal/cli-config.php - tests/Doctrine/Migrations/Tests/Tools/Console/legacy-config-wrong/cli-config.php # Requires PHPUnit 9 - message: '~assert.*Reg~' paths: - tests/Doctrine/Migrations/Tests/Generator/ClassNameGeneratorTest.php symfony: console_application_loader: %currentWorkingDirectory%/tests/Doctrine/Migrations/Tests/doctrine-migrations-phpstan-app.php includes: - vendor/phpstan/phpstan-deprecation-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-symfony/extension.neon
Coded With 💗 by
0x6ick