.gitmodules 0000644 00000000416 14227611130 0006717 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
LICENSE 0000644 00000002037 14227611130 0005550 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.
README.md 0000644 00000004137 14227611130 0006025 0 ustar 00 | [3.0.x][3.0] | [2.12.x][2.12] | [2.11.x][2.11] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.12 image]][2.12] | [![Build status][2.11 image]][2.11] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.12 coverage image]][2.12 coverage] | [![Coverage Status][2.11 coverage image]][2.11 coverage] |
[
πΊπ¦ UKRAINE NEEDS YOUR HELP NOW! ](https://www.doctrine-project.org/stop-war.html)
Doctrine 2 is an object-relational mapper (ORM) 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.12 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.12.x
[2.12]: https://github.com/doctrine/orm/tree/2.12.x
[2.12 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.12.x/graph/badge.svg
[2.12 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.12.x
[2.11 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.11.x
[2.11]: https://github.com/doctrine/orm/tree/2.11.x
[2.11 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.11.x/graph/badge.svg
[2.11 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.11.x
SECURITY.md 0000644 00000001445 14227611130 0006336 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.
UPGRADE.md 0000644 00000133065 14227611130 0006162 0 ustar 00 # 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 element demoted to attribute
We changed how the XML Driver allows to define the change-tracking-policy. The working case is now:
# 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)
bin/doctrine 0000755 00000000076 14227611130 0007051 0 ustar 00 #!/usr/bin/env php
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);
bin/doctrine.bat 0000644 00000000345 14227611130 0007612 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" %*
bin/doctrine.php 0000644 00000002226 14227611130 0007633 0 ustar 00
../../../tests
../../../lib/Doctrine
performance
locking_functional
ci/github/phpunit/pdo_mysql.xml 0000644 00000002376 14227611130 0012646 0 ustar 00
../../../tests
../../../lib/Doctrine
performance
locking_functional
ci/github/phpunit/pdo_pgsql.xml 0000644 00000002412 14227611130 0012616 0 ustar 00
../../../tests
../../../lib/Doctrine
performance
locking_functional
ci/github/phpunit/sqlite.xml 0000644 00000002235 14227611130 0012132 0 ustar 00
../../../tests
../../../lib/Doctrine
performance
locking_functional
composer.json 0000644 00000005346 14227611130 0007273 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",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.1",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3",
"doctrine/lexer": "^1.0",
"doctrine/persistence": "^2.2",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.15"
},
"require-dev": {
"doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.5.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.22.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"
},
"suggest": {
"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"]
}
}
doctrine-mapping.xsd 0000644 00000071654 14227611130 0010536 0 ustar 00
lib/Doctrine/ORM/AbstractQuery.php 0000644 00000115525 14227611130 0013026 0 ustar 00
*/
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
*/
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 AbstractQuery .
*/
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 enable 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 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
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Gets a query parameter.
*
* @param mixed $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|mixed[] $parameters
*
* @return $this
*/
public function setParameters($parameters)
{
// BC compatibility with 2.3-
if (is_array($parameters)) {
/** @psalm-var ArrayCollection $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[]|string|int|float|bool
* @psalm-return array|scalar
*
* @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 (! is_object($value)) {
return $value;
}
try {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
} 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) {
$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) {
$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 ($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.
*
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
*
* @param string $class
* @param string $assocName
* @param int $fetchMode
*
* @return $this
*/
public function setFetchMode($class, $assocName, $fetchMode)
{
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
$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
*/
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 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|mixed[] $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return iterable
*/
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|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|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|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 = [];
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
}
$sql = $this->getSQL();
$queryCacheProfile = $this->getHydrationCacheProfile();
$hints = $this->getHints();
$hints['hydrationMode'] = $this->getHydrationMode();
ksort($hints);
assert($queryCacheProfile !== null);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $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();
$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 $subject */
private function isCountable(iterable $subject): bool
{
return $subject instanceof Countable || is_array($subject);
}
}
lib/Doctrine/ORM/Cache.php 0000644 00000011300 14227611130 0011222 0 ustar 00 The entity identifier
*/
public $identifier;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
*/
public $class;
/**
* @param string $class The entity class.
* @param array $identifier The entity identifier.
*/
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 $values array containing property values
*
* @return AssociationCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['class'], $values['identifier']);
}
}
lib/Doctrine/ORM/Cache/CacheConfiguration.php 0000644 00000003663 14227611130 0014772 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Cache/CacheEntry.php 0000644 00000000354 14227611130 0013256 0 ustar 00 IMPORTANT NOTE:
*
* Fields of classes that implement CacheEntry are public for performance reason.
*/
interface CacheEntry
{
}
lib/Doctrine/ORM/Cache/CacheException.php 0000644 00000002555 14227611130 0014120 0 ustar 00 $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);
}
lib/Doctrine/ORM/Cache/CacheKey.php 0000644 00000000621 14227611130 0012702 0 ustar 00 identifiers = $identifiers;
}
/**
* Creates a new CollectionCacheEntry
*
* This method allows for Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return CollectionCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['identifiers']);
}
}
lib/Doctrine/ORM/Cache/CollectionCacheKey.php 0000644 00000002775 14227611130 0014732 0 ustar 00 The owner entity identifier
*/
public $ownerIdentifier;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The owner entity class
*/
public $entityClass;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The association name
*/
public $association;
/**
* @param string $entityClass The entity class.
* @param string $association The field name that represents the association.
* @param array $ownerIdentifier The identifier of the owning entity.
*/
public function __construct($entityClass, $association, array $ownerIdentifier)
{
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
}
}
lib/Doctrine/ORM/Cache/CollectionHydrator.php 0000644 00000002205 14227611130 0015036 0 ustar 00 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 ClassMetadata $metadata The entity metadata.
* @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 ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @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 ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return array
*/
private function toIdentifierArray(ClassMetadata $metadata, $identifier): array
{
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
$identifier = $this->uow->getSingleIdentifierValue($identifier);
if ($identifier === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
return [$metadata->identifier[0] => $identifier];
}
}
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php 0000644 00000020411 14227611130 0015065 0 ustar 00 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)
{
$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);
}
}
lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php 0000644 00000004513 14227611130 0016347 0 ustar 00 */
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;
}
}
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php 0000644 00000015053 14227611130 0015531 0 ustar 00 */
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) {
$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;
}
}
lib/Doctrine/ORM/Cache/DefaultQueryCache.php 0000644 00000037060 14227611130 0014573 0 ustar 00 */
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 array $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 array|object
*/
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
* @param array $path
*
* @return mixed
*/
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;
}
}
lib/Doctrine/ORM/Cache/EntityCacheEntry.php 0000644 00000003311 14227611130 0014447 0 ustar 00 The entity map data
*/
public $data;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
* @psalm-var class-string
*/
public $class;
/**
* @param string $class The entity class.
* @param array $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 $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
*/
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);
}
}
lib/Doctrine/ORM/Cache/EntityCacheKey.php 0000644 00000002212 14227611130 0014075 0 ustar 00 The entity identifier
*/
public $identifier;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
*/
public $entityClass;
/**
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
* @param array $identifier The entity identifier
*/
public function __construct($entityClass, array $identifier)
{
ksort($identifier);
$this->identifier = $identifier;
$this->entityClass = $entityClass;
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
}
}
lib/Doctrine/ORM/Cache/EntityHydrator.php 0000644 00000001725 14227611130 0014225 0 ustar 00 value = $value;
$this->time = $time ?: time();
}
/**
* @return Lock
*/
public static function createLockRead()
{
return new self(uniqid((string) time(), true));
}
}
lib/Doctrine/ORM/Cache/LockException.php 0000644 00000000306 14227611130 0013775 0 ustar 00 */
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
*/
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);
}
}
}
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php 0000644 00000012723 14227611130 0017040 0 ustar 00 cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
? $this->cacheHitCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
? $this->cacheMissCountMap[$regionName] + 1
: 1;
}
/**
* {@inheritdoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
? $this->cachePutCountMap[$regionName] + 1
: 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
*/
public function getRegionsMiss()
{
return $this->cacheMissCountMap;
}
/**
* @return array
*/
public function getRegionsHit()
{
return $this->cacheHitCountMap;
}
/**
* @return array
*/
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);
}
}
lib/Doctrine/ORM/Cache/MultiGetRegion.php 0000644 00000001144 14227611130 0014125 0 ustar 00 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;
}
/**
* @return object[]|null
*/
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
*
* @return void
*/
protected function evictCollectionCache(PersistentCollection $collection)
{
$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);
}
}
/**
* @param string $targetEntity
* @param object $element
*
* @return void
*/
protected function evictElementCache($targetEntity, $element)
{
$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);
}
}
}
lib/Doctrine/ORM/Cache/Persister/Collection/CachedCollectionPersister.php 0000644 00000002135 14227611130 0022367 0 ustar 00 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,
];
}
}
lib/Doctrine/ORM/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php 0000644 00000001354 14227611130 0024027 0 ustar 00 isDirty() && $collection->getSnapshot()) {
throw CannotUpdateReadOnlyCollection::fromEntityAndField(
ClassUtils::getClass($collection->getOwner()),
$this->association['fieldName']
);
}
parent::update($collection);
}
}
lib/Doctrine/ORM/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php 0000644 00000006471 14227611130 0024205 0 ustar 00 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,
];
}
}
lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php 0000644 00000044310 14227611130 0021326 0 ustar 00 |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[] $orderBy
* @param int $limit
* @param int $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 $association
* @param array $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);
}
}
lib/Doctrine/ORM/Cache/Persister/Entity/CachedEntityPersister.php 0000644 00000001166 14227611130 0020734 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php 0000644 00000000754 14227611130 0022374 0 ustar 00 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,
];
}
}
lib/Doctrine/ORM/Cache/QueryCache.php 0000644 00000001435 14227611130 0013263 0 ustar 00 List of entity identifiers
*/
public $result;
/**
* @readonly Public only for performance reasons, it should be considered immutable.
* @var float Time creation of this cache entry
*/
public $time;
/**
* @param array $result
* @param float $time
*/
public function __construct($result, $time = null)
{
$this->result = $result;
$this->time = $time ?: microtime(true);
}
/**
* @param array $values
*
* @return QueryCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['result'], $values['time']);
}
}
lib/Doctrine/ORM/Cache/QueryCacheKey.php 0000644 00000002166 14227611130 0013736 0 ustar 00 hash = $cacheId;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
$this->timestampKey = $timestampKey;
}
}
lib/Doctrine/ORM/Cache/QueryCacheValidator.php 0000644 00000000454 14227611130 0015131 0 ustar 00 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 $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, '{}()/\@:', '________');
}
}
lib/Doctrine/ORM/Cache/Region/FileLockRegion.php 0000644 00000012371 14227611130 0015312 0 ustar 00 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));
}
}
lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php 0000644 00000000755 14227611130 0016513 0 ustar 00 put($key, new TimestampCacheEntry());
}
}
lib/Doctrine/ORM/Cache/RegionsConfiguration.php 0000644 00000004276 14227611130 0015376 0 ustar 00 */
private $lifetimes = [];
/** @var array */
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;
}
}
lib/Doctrine/ORM/Cache/TimestampCacheEntry.php 0000644 00000001522 14227611130 0015140 0 ustar 00 time = $time ? (float) $time : microtime(true);
}
/**
* Creates a new TimestampCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return TimestampCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['time']);
}
}
lib/Doctrine/ORM/Cache/TimestampCacheKey.php 0000644 00000000472 14227611130 0014572 0 ustar 00 hash = (string) $space;
}
}
lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php 0000644 00000001757 14227611130 0017024 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Cache/TimestampRegion.php 0000644 00000000763 14227611130 0014344 0 ustar 00 _attributes['proxyDir'] = $dir;
}
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @return string|null
*/
public function getProxyDir()
{
return $this->_attributes['proxyDir'] ?? null;
}
/**
* Gets the strategy for automatically generating proxy classes.
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
*/
public function getAutoGenerateProxyClasses()
{
return $this->_attributes['autoGenerateProxyClasses'] ?? AbstractProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* 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.
*
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
*
* @see https://github.com/Ocramius/ProxyManager
*
* @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.
*
* @param string|string[] $paths
* @param bool $useSimpleAnnotationReader
* @psalm-param string|list $paths
*
* @return AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
{
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.'
));
}
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
if ($useSimpleAnnotationReader) {
// Register the ORM Annotations in the AnnotationRegistry
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
} else {
$reader = new AnnotationReader();
}
if (class_exists(ArrayCache::class)) {
$reader = new CachedReader($reader, new ArrayCache());
}
return new AnnotationDriver(
$reader,
(array) $paths
);
}
/**
* Adds a namespace under a certain alias.
*
* @param string $alias
* @param string $namespace
*
* @return void
*/
public function addEntityNamespace($alias, $namespace)
{
$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 $entityNamespaces
*
* @return void
*/
public function setEntityNamespaces(array $entityNamespaces)
{
$this->_attributes['entityNamespaces'] = $entityNamespaces;
}
/**
* Retrieves the list of registered entity namespace aliases.
*
* @psalm-return array
*/
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()) {
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 string|callable $className Class name or a callable that returns the function.
*
* @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|null
* @psalm-return ?class-string
*/
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 $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 string|callable $className Class name or a callable that returns the function.
*
* @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|null
* @psalm-return ?class-string
*/
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 $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|callable $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|null
* @psalm-return ?class-string $name
*/
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 $functions
*
* @return void
*/
public function setCustomDatetimeFunctions(array $functions)
{
foreach ($functions as $name => $className) {
$this->addCustomDatetimeFunction($name, $className);
}
}
/**
* Sets the custom hydrator modes in one pass.
*
* @param array> $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|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 $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.
*
* @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
*/
public function getFilterClassName($name)
{
return $this->_attributes['filters'][$name] ?? null;
}
/**
* Sets default repository class.
*
* @param string $className
*
* @return void
*
* @throws InvalidEntityRepository If $classname is not an ObjectRepository.
*/
public function setDefaultRepositoryClassName($className)
{
$reflectionClass = new ReflectionClass($className);
if (! $reflectionClass->implementsInterface(ObjectRepository::class)) {
throw InvalidEntityRepository::fromClassName($className);
}
$this->_attributes['defaultRepositoryClassName'] = $className;
}
/**
* Get default repository class.
*
* @return string
* @psalm-return class-string
*/
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
*/
public function getDefaultQueryHints()
{
return $this->_attributes['defaultQueryHints'] ?? [];
}
/**
* Sets array of query hints, which will be applied to every query in application
*
* @psalm-param array $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
*/
public function getSchemaIgnoreClasses(): array
{
return $this->_attributes['schemaIgnoreClasses'] ?? [];
}
/**
* Sets a list of entity class names to be ignored by the SchemaTool
*
* @param list $schemaIgnoreClasses List of entity class names
*/
public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void
{
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
}
lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php 0000644 00000012440 14227611130 0016561 0 ustar 00
*/
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}
*/
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 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();
}
}
lib/Doctrine/ORM/EntityManager.php 0000644 00000071244 14227611130 0013003 0 ustar 00 'pdo_sqlite', 'memory' => true);
* $entityManager = EntityManager::create($dbParams, $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.
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$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 = call_user_func($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 $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) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($value))) {
$id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
if ($id[$i] === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
}
$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));
}
$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.
*
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function refresh($entity)
{
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
}
$this->errorIfClosed();
$this->unitOfWork->refresh($entity);
}
/**
* 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}
*/
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 $entityName
*
* @return ObjectRepository|EntityRepository The repository class.
* @psalm-return EntityRepository
*
* @template T of object
*/
public function getRepository($entityName)
{
return $this->repositoryFactory->getRepository($this, $entityName);
}
/**
* 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.
*
* @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|Connection $connection
*
* @return EntityManager The created EntityManager.
*
* @throws InvalidArgumentException
* @throws ORMException
*/
public static function create($connection, Configuration $config, ?EventManager $eventManager = null)
{
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
}
$connection = static::createConnection($connection, $config, $eventManager);
return new EntityManager($connection, $config, $connection->getEventManager());
}
/**
* Factory method to create Connection instances.
*
* @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|Connection $connection
*
* @return Connection
*
* @throws InvalidArgumentException
* @throws ORMException
*/
protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null)
{
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));
}
}
lib/Doctrine/ORM/EntityManagerInterface.php 0000644 00000022776 14227611130 0014632 0 ustar 00 $className
*
* @psalm-return EntityRepository
*
* @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:
*
*
* $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)));
*
*
* @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 $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 $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 $className
*
* @return Mapping\ClassMetadata
* @psalm-return Mapping\ClassMetadata
*
* @psalm-template T of object
*/
public function getClassMetadata($className);
}
lib/Doctrine/ORM/EntityNotFoundException.php 0000644 00000002076 14227611130 0015041 0 ustar 00 $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
));
}
}
lib/Doctrine/ORM/EntityRepository.php 0000644 00000024251 14227611130 0013604 0 ustar 00
* @template-implements ObjectRepository
*/
class EntityRepository implements ObjectRepository, Selectable
{
/** @var string */
protected $_entityName;
/** @var EntityManagerInterface */
protected $_em;
/** @var ClassMetadata */
protected $_class;
/** @var Inflector */
private static $inflector;
/**
* Initializes a new EntityRepository .
*/
public function __construct(EntityManagerInterface $em, Mapping\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 $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__
);
$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 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 $criteria
* @psalm-param array|null $orderBy
*
* @return object[] The objects.
* @psalm-return list
*/
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 $criteria
* @psalm-param array|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 $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 $arguments
*
* @return mixed The returned value from the resolved method.
*
* @throws BadMethodCallException If the method called is invalid.
*/
public function __call($method, $arguments)
{
if (strpos($method, 'findBy') === 0) {
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
}
if (strpos($method, 'findOneBy') === 0) {
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
}
if (strpos($method, 'countBy') === 0) {
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
*/
protected function getEntityName()
{
return $this->_entityName;
}
/**
* @return string
*/
public function getClassName()
{
return $this->getEntityName();
}
/**
* @return EntityManagerInterface
*/
protected function getEntityManager()
{
return $this->_em;
}
/**
* @return Mapping\ClassMetadata
*/
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 LazyCriteriaCollection
* @psalm-return Collection
*/
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 $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));
}
}
lib/Doctrine/ORM/Event/LifecycleEventArgs.php 0000644 00000001351 14227611130 0015023 0 ustar 00 getObject();
}
/**
* Retrieves associated EntityManager.
*
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->getObjectManager();
}
}
lib/Doctrine/ORM/Event/ListenersInvoker.php 0000644 00000005664 14227611130 0014626 0 ustar 00 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.
*/
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.
*
* @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);
}
}
}
lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php 0000644 00000001320 14227611130 0016426 0 ustar 00 getObjectManager();
}
}
lib/Doctrine/ORM/Event/OnClassMetadataNotFoundEventArgs.php 0000644 00000002553 14227611130 0017611 0 ustar 00 className = (string) $className;
parent::__construct($objectManager);
}
/**
* @return void
*/
public function setFoundMetadata(?ClassMetadata $classMetadata = null)
{
$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;
}
}
lib/Doctrine/ORM/Event/OnClearEventArgs.php 0000644 00000002310 14227611130 0014443 0 ustar 00 em = $em;
$this->entityClass = $entityClass;
}
/**
* Retrieves associated EntityManager.
*
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->em;
}
/**
* Name of the entity class that is cleared, or empty if all are cleared.
*
* @return string|null
*/
public function getEntityClass()
{
return $this->entityClass;
}
/**
* Checks if event clears all entities.
*
* @return bool
*/
public function clearsAllEntities()
{
return $this->entityClass === null;
}
}
lib/Doctrine/ORM/Event/OnFlushEventArgs.php 0000644 00000001155 14227611130 0014504 0 ustar 00 em = $em;
}
/**
* Retrieve associated EntityManager.
*
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->em;
}
}
lib/Doctrine/ORM/Event/PostFlushEventArgs.php 0000644 00000001161 14227611130 0015052 0 ustar 00 em = $em;
}
/**
* Retrieves associated EntityManager.
*
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->em;
}
}
lib/Doctrine/ORM/Event/PreFlushEventArgs.php 0000644 00000001075 14227611130 0014657 0 ustar 00 em = $em;
}
/**
* @return EntityManagerInterface
*/
public function getEntityManager()
{
return $this->em;
}
}
lib/Doctrine/ORM/Event/PreUpdateEventArgs.php 0000644 00000005157 14227611130 0015025 0 ustar 00 */
private $entityChangeSet;
/**
* @param object $entity
* @param mixed[][] $changeSet
* @psalm-param array $changeSet
*/
public function __construct($entity, EntityManagerInterface $em, array &$changeSet)
{
parent::__construct($entity, $em);
$this->entityChangeSet = &$changeSet;
}
/**
* Retrieves entity changeset.
*
* @return mixed[][]
* @psalm-return array
*/
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->getEntity())
));
}
}
}
lib/Doctrine/ORM/Events.php 0000644 00000010104 14227611130 0011464 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Id/AssignedGenerator.php 0000644 00000002431 14227611130 0014164 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Id/BigIntegerIdentityGenerator.php 0000644 00000002361 14227611130 0016162 0 ustar 00 sequenceName = $sequenceName;
}
/**
* {@inheritDoc}
*/
public function generateId(EntityManagerInterface $em, $entity)
{
return (string) $em->getConnection()->lastInsertId($this->sequenceName);
}
/**
* {@inheritDoc}
*/
public function isPostInsertGenerator()
{
return true;
}
}
lib/Doctrine/ORM/Id/IdentityGenerator.php 0000644 00000002344 14227611130 0014223 0 ustar 00 sequenceName = $sequenceName;
}
/**
* {@inheritDoc}
*/
public function generateId(EntityManagerInterface $em, $entity)
{
return (int) $em->getConnection()->lastInsertId($this->sequenceName);
}
/**
* {@inheritdoc}
*/
public function isPostInsertGenerator()
{
return true;
}
}
lib/Doctrine/ORM/Id/SequenceGenerator.php 0000644 00000005766 14227611130 0014215 0 ustar 00 _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->executeQuery($sql)->fetchOne();
$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
*/
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 $data
*/
public function __unserialize(array $data): void
{
$this->_sequenceName = $data['sequenceName'];
$this->_allocationSize = $data['allocationSize'];
}
}
lib/Doctrine/ORM/Id/TableGenerator.php 0000644 00000004733 14227611130 0013465 0 ustar 00 _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++;
}
}
lib/Doctrine/ORM/Id/UuidGenerator.php 0000644 00000002612 14227611130 0013336 0 ustar 00 getConnection();
$sql = 'SELECT ' . $connection->getDatabasePlatform()->getGuidExpression();
if ($connection instanceof PrimaryReadReplicaConnection) {
$connection->ensureConnectedToPrimary();
}
return $connection->executeQuery($sql)->fetchOne();
}
}
lib/Doctrine/ORM/Internal/CommitOrderCalculator.php 0000644 00000011640 14227611130 0016240 0 ustar 00 state (integer)
* Whether the node is NOT_VISITED or IN_PROGRESS
*
* - value (object)
* Actual node value
*
* - dependencyList (array)
* Map of node dependencies defined as hashes.
*
* @var array
*/
private $nodeList = [];
/**
* Volatile variable holding calculated nodes during sorting process.
*
* @psalm-var list
*/
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 object $node
*
* @return void
*/
public function addNode($hash, $node)
{
$vertex = new stdClass();
$vertex->hash = $hash;
$vertex->state = self::NOT_VISITED;
$vertex->value = $node;
$vertex->dependencyList = [];
$this->nodeList[$hash] = $vertex;
}
/**
* 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)
{
$vertex = $this->nodeList[$fromHash];
$edge = new stdClass();
$edge->from = $fromHash;
$edge->to = $toHash;
$edge->weight = $weight;
$vertex->dependencyList[$toHash] = $edge;
}
/**
* 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
*/
public function sort()
{
foreach ($this->nodeList as $vertex) {
if ($vertex->state !== self::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(stdClass $vertex): void
{
$vertex->state = self::IN_PROGRESS;
foreach ($vertex->dependencyList as $edge) {
$adjacentVertex = $this->nodeList[$edge->to];
switch ($adjacentVertex->state) {
case self::VISITED:
// Do nothing, since node was already visited
break;
case self::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 === self::NOT_VISITED) {
$this->visit($adjacentEdgeVertex);
}
}
$adjacentVertex->state = self::VISITED;
$this->sortedNodeList[] = $adjacentVertex->value;
}
break;
case self::NOT_VISITED:
$this->visit($adjacentVertex);
}
}
if ($vertex->state !== self::VISITED) {
$vertex->state = self::VISITED;
$this->sortedNodeList[] = $vertex->value;
}
}
}
lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php 0000644 00000053160 14227611130 0017226 0 ustar 00 >
*/
protected $_metadataCache = [];
/**
* The cache used during row-by-row hydration.
*
* @var array
*/
protected $_cache = [];
/**
* The statement that provides the data to hydrate.
*
* @var Result|null
*/
protected $_stmt;
/**
* The query hints.
*
* @var array
*/
protected $_hints = [];
/**
* Initializes a new instance of a class derived from AbstractHydrator .
*
* @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 $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 $hints
*
* @return Generator
*
* @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 $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 $id Dql-Alias => ID-Hash.
* @psalm-param array $nonemptyComponents Does this DQL-Alias has at least one non NULL value?
*
* @return array> An array with all the fields
* (name => value) of the data
* row, grouped by their
* component alias.
* @psalm-return array{
* data: array,
* newObjects?: 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);
$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);
$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 ($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 $data
*
* @return mixed[] The processed row.
* @psalm-return array
*/
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|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,
];
// 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']),
];
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' => '',
];
case isset($this->_rsm->scalarMappings[$key]):
return $this->_cache[$key] = [
'isScalar' => true,
'fieldName' => $this->_rsm->scalarMappings[$key],
'type' => Type::getType($this->_rsm->typeMappings[$key]),
];
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,
];
}
// 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
*/
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);
}
}
lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php 0000644 00000023301 14227611130 0016533 0 ustar 00 */
private $_rootAliases = [];
/** @var bool */
private $_isSimpleQuery = false;
/** @var mixed[] */
private $_identifierMap = [];
/** @var mixed[] */
private $_resultPointers = [];
/** @var array */
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)];
}
}
lib/Doctrine/ORM/Internal/Hydration/HydrationException.php 0000644 00000005262 14227611130 0017566 0 ustar 00 $discrMap
*
* @return HydrationException
*/
public static function invalidDiscriminatorValue($discrValue, $discrMap)
{
return new self(sprintf(
'The discriminator value "%s" is invalid. It must be one of "%s".',
$discrValue,
implode('", "', $discrMap)
));
}
}
lib/Doctrine/ORM/Internal/Hydration/IterableResult.php 0000644 00000003342 14227611130 0016671 0 ustar 00 _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;
}
}
lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php 0000644 00000057500 14227611130 0016673 0 ustar 00 _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->existingCollections =
$this->resultPointers = [];
if ($eagerLoad) {
$this->_uow->triggerEagerLoads();
}
$this->_uow->hydrationComplete();
}
protected function cleanupAfterRowIteration(): void
{
$this->identifierMap =
$this->initializedCollections =
$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();
}
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 $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 = (string) $data[$discrColumn];
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 $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 = '';
foreach ($class->identifier as $fieldName) {
$idHash .= ' ' . (isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName]);
}
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 {
$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) {
$reflFieldValue->setInitialized(true);
}
} 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, []);
}
}
lib/Doctrine/ORM/Internal/Hydration/ScalarColumnHydrator.php 0000644 00000001432 14227611130 0020041 0 ustar 00 resultSetMapping()->fieldMappings) > 1) {
throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings);
}
$result = $this->statement()->fetchAllNumeric();
return array_column($result, 0);
}
}
lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php 0000644 00000001436 14227611130 0016667 0 ustar 00 statement()->fetchAssociative()) {
$this->hydrateRowData($data, $result);
}
return $result;
}
/**
* {@inheritdoc}
*/
protected function hydrateRowData(array $row, array &$result)
{
$result[] = $this->gatherScalarRowData($row);
}
}
lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php 0000644 00000012412 14227611130 0020036 0 ustar 00 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);
}
$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();
}
}
}
lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php 0000644 00000002201 14227611130 0020020 0 ustar 00 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);
}
}
lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php 0000644 00000004053 14227611130 0016732 0 ustar 00 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 me 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 LifecycleEventArgs($entity, $this->em),
$invoke
);
}
}
}
lib/Doctrine/ORM/Internal/SQLResultCasing.php 0000644 00000001573 14227611130 0014771 0 ustar 00 getSQLResultCasing($column);
}
return $column;
}
}
lib/Doctrine/ORM/LazyCriteriaCollection.php 0000644 00000005110 14227611130 0014637 0 ustar 00 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();
}
/**
* Do an optimized search of an element
*
* @param object $element
*
* @return bool
*/
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();
return $this->collection->matching($criteria);
}
/**
* {@inheritDoc}
*/
protected function doInitialize()
{
$elements = $this->entityPersister->loadCriteria($this->criteria);
$this->collection = new ArrayCollection($elements);
}
}
lib/Doctrine/ORM/Mapping/Annotation.php 0000644 00000000133 14227611130 0013726 0 ustar 00 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);
}
}
lib/Doctrine/ORM/Mapping/AssociationOverride.php 0000644 00000004140 14227611130 0015572 0 ustar 00 |null
*/
public $joinColumns;
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
*/
public $inverseJoinColumns;
/**
* The join table that maps the relationship.
*
* @var \Doctrine\ORM\Mapping\JoinTable|null
*/
public $joinTable;
/**
* The name of the association-field on the inverse-side.
*
* @var ?string
*/
public $inversedBy;
/**
* The fetching strategy to use for the association.
*
* @var ?string
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch;
/**
* @param JoinColumn|array $joinColumns
* @param JoinColumn|array $inverseJoinColumns
*/
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;
}
}
lib/Doctrine/ORM/Mapping/AssociationOverrides.php 0000644 00000001770 14227611130 0015763 0 ustar 00
*/
public $overrides = [];
/**
* @param array|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[] = $override;
}
}
}
lib/Doctrine/ORM/Mapping/AttributeOverride.php 0000644 00000001220 14227611130 0015255 0 ustar 00 name = $name;
$this->column = $column;
}
}
lib/Doctrine/ORM/Mapping/AttributeOverrides.php 0000644 00000001740 14227611130 0015447 0 ustar 00
*/
public $overrides = [];
/**
* @param array|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[] = $override;
}
}
}
lib/Doctrine/ORM/Mapping/Builder/AssociationBuilder.php 0000644 00000010445 14227611130 0016774 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Mapping/Builder/ClassMetadataBuilder.php 0000644 00000027056 14227611130 0017234 0 ustar 00 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 $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 $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
*
* @return $this
*/
public function setDiscriminatorColumn($name, $type = 'string', $length = 255)
{
$this->cm->setDiscriminatorColumn(
[
'name' => $name,
'type' => $type,
'length' => $length,
]
);
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 $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();
}
}
lib/Doctrine/ORM/Mapping/Builder/EmbeddedBuilder.php 0000644 00000002210 14227611130 0016200 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Mapping/Builder/EntityListenerBuilder.php 0000644 00000003043 14227611130 0017476 0 ustar 00 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);
}
}
}
lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php 0000644 00000012647 14227611130 0015551 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Mapping/Builder/ManyToManyAssociationBuilder.php 0000644 00000003721 14227611130 0020750 0 ustar 00 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;
}
}
lib/Doctrine/ORM/Mapping/Builder/OneToManyAssociationBuilder.php 0000644 00000001761 14227611130 0020567 0 ustar 00 $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;
}
}
lib/Doctrine/ORM/Mapping/Cache.php 0000644 00000001420 14227611130 0012617 0 ustar 00 usage = $usage;
$this->region = $region;
}
}
lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php 0000644 00000001100 14227611130 0015637 0 ustar 00 value = $value;
}
}
lib/Doctrine/ORM/Mapping/ClassMetadata.php 0000644 00000001370 14227611130 0014326 0 ustar 00
*/
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 $entityName
*/
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
{
parent::__construct($entityName, $namingStrategy);
}
}
lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php 0000644 00000065365 14227611130 0015674 0 ustar 00
*/
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->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) {
foreach ($class->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
}
if (! (isset($embeddableClass['class']) && $embeddableClass['class'])) {
throw MappingException::missingEmbeddedClass($property);
}
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 (! 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);
}
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
if ($class->changeTrackingPolicy === ClassMetadataInfo::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());
}
/**
* 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);
}
/**
* Gets the lower-case short name of a class.
*
* @psalm-param class-string $className
*/
private function getShortName(string $className): string
{
if (strpos($className, '\\') === false) {
return strtolower($className);
}
$parts = explode('\\', $className);
return strtolower(end($parts));
}
/**
* Adds inherited fields to the subclass mapping.
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->fieldMappings as $mapping) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$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) {
if ($parentClass->isMappedSuperclass) {
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedAssociationMapping($mapping);
}
}
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
if (! isset($embeddedClass['inherited']) && ! $parentClass->isMappedSuperclass) {
$embeddedClass['inherited'] = $parentClass->name;
}
if (! isset($embeddedClass['declared'])) {
$embeddedClass['declared'] = $parentClass->name;
}
$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;
// Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
if ($this->getTargetPlatform()->usesSequenceEmulatedIdentityColumns()) {
$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);
}
/**
* {@inheritDoc}
*/
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;
}
}
lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php 0000644 00000357367 14227611130 0015166 0 ustar 00 ClassMetadata 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.
*
* IMPORTANT NOTE:
*
* 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
* @psalm-type FieldMapping = array{
* type: string,
* fieldName: string,
* columnName: string,
* length?: int,
* id?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: string,
* enumType?: class-string,
* columnDefinition?: string,
* precision?: int,
* scale?: int,
* unique?: string,
* inherited?: class-string,
* originalClass?: class-string,
* originalField?: string,
* quoted?: bool,
* requireSQLConversion?: bool,
* declared?: class-string,
* declaredField?: string,
* options?: array
* }
*/
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
* Class Table Inheritance .
*/
public const INHERITANCE_TYPE_JOINED = 2;
/**
* SINGLE_TABLE means the class will be persisted according to the rules of
* Single Table Inheritance .
*/
public const INHERITANCE_TYPE_SINGLE_TABLE = 3;
/**
* TABLE_PER_CLASS means the class will be persisted according to the rules
* of Concrete Table Inheritance .
*/
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 NotifyPropertyChanged 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
*/
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:
*
* array(
* 'class' => 'ClassName',
* )
*
*
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
* @var array|null
*/
public $customGeneratorDefinition;
/**
* The name of the custom repository class used for the entity class.
* (Optional).
*
* @var string|null
* @psalm-var ?class-string
*/
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 classes (ancestors).
*
* @psalm-var list
*/
public $parentClasses = [];
/**
* READ-ONLY: The names of all subclasses (descendants).
*
* @psalm-var list
*/
public $subClasses = [];
/**
* READ-ONLY: The names of all embedded classes based on properties.
*
* @psalm-var array
*/
public $embeddedClasses = [];
/**
* READ-ONLY: The named queries allowed to be called directly from Repository.
*
* @psalm-var array>
*/
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:
*
* array(
* 'name' => ,
* 'query' => ,
* 'resultClass' => ,
* 'resultSetMapping' =>
* )
*
*
* @psalm-var array>
*/
public $namedNativeQueries = [];
/**
* READ-ONLY: The mappings of the results of native SQL queries.
*
* A native result mapping definition has the following structure:
*
* array(
* 'name' => ,
* 'entities' => array(),
* 'columns' => array()
* )
*
*
* @psalm-var array
*/
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
*/
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:
*
* - fieldName (string)
* The name of the field in the Entity.
*
* - type (string)
* The type name of the mapped field. Can be one of Doctrine's mapping types
* or a custom mapping type.
*
* - columnName (string, optional)
* The column name. Optional. Defaults to the field name.
*
* - length (integer, optional)
* The database length of the column. Optional. Default value taken from
* the type.
*
* - id (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.
*
* - nullable (boolean, optional)
* Whether the column is nullable. Defaults to FALSE.
*
* - 'notInsertable' (boolean, optional)
* Whether the column is not insertable. Optional. Is only set if value is TRUE.
*
* - 'notUpdatable' (boolean, optional)
* Whether the column is updatable. Optional. Is only set if value is TRUE.
*
* - columnDefinition (string, optional, schema-only)
* The SQL fragment that is used when generating the DDL for the column.
*
* - precision (integer, optional, schema-only)
* The precision of a decimal column. Only valid if the column type is decimal.
*
* - scale (integer, optional, schema-only)
* The scale of a decimal column. Only valid if the column type is decimal.
*
* - 'unique' (string, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* @var mixed[]
* @psalm-var array
*/
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
*/
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.
*
* This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
* where a discriminator column is used.
*
* @see discriminatorColumn
*
* @var mixed
*/
public $discriminatorValue;
/**
* READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
*
* This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
* where a discriminator column is used.
*
* @see discriminatorColumn
*
* @var mixed
*/
public $discriminatorMap = [];
/**
* READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings.
*
* @psalm-var array|null
*/
public $discriminatorColumn;
/**
* READ-ONLY: The primary table definition. The definition is an array with the
* following entries:
*
* name =>
* schema =>
* indexes => array
* uniqueConstraints => array
*
* @var mixed[]
* @psalm-var array{
* name: string,
* schema: string,
* indexes: array,
* uniqueConstraints: array,
* options: array,
* quoted?: bool
* }
*/
public $table;
/**
* READ-ONLY: The registered lifecycle callbacks for entities of this class.
*
* @psalm-var array>
*/
public $lifecycleCallbacks = [];
/**
* READ-ONLY: The registered entity listeners.
*
* @psalm-var array>
*/
public $entityListeners = [];
/**
* READ-ONLY: The association mappings of this class.
*
* The mapping definition array supports the following keys:
*
* - fieldName (string)
* The name of the field in the entity the association is mapped to.
*
* - targetEntity (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.
*
* - mappedBy (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.
*
* - inversedBy (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.
*
* - cascade (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).
*
* - orderBy (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')
*
* - fetch (integer, optional)
* The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
* Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
*
* - joinTable (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.
*
* - indexBy (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.
*
* A join table definition has the following structure:
*
* array(
* 'name' => ,
* 'joinColumns' => array(),
* 'inverseJoinColumns' => array()
* )
*
*
* @psalm-var array>
*/
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: 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:
*
* array(
* 'sequenceName' => 'name',
* 'allocationSize' => '20',
* 'initialValue' => '1'
* )
*
*
* @var array
* @psalm-var array{sequenceName: string, allocationSize: string, initialValue: string, quoted?: mixed}
* @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
*/
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 mixed
*/
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
*/
public $reflFields = [];
/** @var InstantiatorInterface|null */
private $instantiator;
/**
* 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 $entityName
*/
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
{
$this->name = $entityName;
$this->rootEntityName = $entityName;
$this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
$this->instantiator = new Instantiator();
}
/**
* Gets the ReflectionProperties of the mapped class.
*
* @return ReflectionProperty[]|null[] An array of ReflectionProperty instances.
* @psalm-return array
*/
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
*/
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 $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->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'])) {
$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, 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 array
*
* @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>
*/
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>
*/
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
*
* @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>
*/
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
*/
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 mixed[] $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
*/
private function validateAndCompleteTypedFieldMapping(array $mapping): array
{
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type) {
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);
}
switch ($type->getName()) {
case DateInterval::class:
$mapping['type'] = Types::DATEINTERVAL;
break;
case DateTime::class:
$mapping['type'] = Types::DATETIME_MUTABLE;
break;
case DateTimeImmutable::class:
$mapping['type'] = Types::DATETIME_IMMUTABLE;
break;
case 'array':
$mapping['type'] = Types::JSON;
break;
case 'bool':
$mapping['type'] = Types::BOOLEAN;
break;
case 'float':
$mapping['type'] = Types::FLOAT;
break;
case 'int':
$mapping['type'] = Types::INTEGER;
break;
case 'string':
$mapping['type'] = Types::STRING;
break;
}
}
}
return $mapping;
}
/**
* Validates & completes the basic mapping information based on typed property.
*
* @param mixed[] $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 $mapping The field mapping to validate & complete.
*
* @return mixed[] 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']);
}
}
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 $mapping The mapping.
*
* @return mixed[] The updated mapping.
* @psalm-return array{
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* ?orphanRemoval: bool
* }
*
* @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 $mapping The mapping to validate & complete.
* @psalm-param array $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
* @psalm-return array{
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinColumns?: array{0: array{name: string, referencedColumnName: string}}|mixed,
* id?: mixed,
* sourceToTargetKeyColumns?: array,
* joinColumnFieldNames?: array,
* targetToSourceKeyColumns?: array,
* orphanRemoval: bool
* }
*
* @throws RuntimeException
* @throws MappingException
*/
protected function _validateAndCompleteOneToOneMapping(array $mapping)
{
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
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 $mapping The mapping to validate and complete.
*
* @return mixed[] The validated and completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* isOwningSide: bool,
* sourceEntity: string,
* targetEntity: string,
* fieldName: mixed,
* fetch: int|mixed,
* cascade: array,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* orphanRemoval: bool
* }
*
* @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 $mapping The mapping to validate & complete.
* @psalm-param array $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinTable?: array{inverseJoinColumns: mixed}|mixed,
* joinTableColumns?: list,
* isOnDeleteCascade?: true,
* relationToSourceKeyColumns?: array,
* relationToTargetKeyColumns?: array,
* orphanRemoval: bool
* }
*
* @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 $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|null $fieldNames
*
* @return mixed[]
* @psalm-return list
*/
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
*/
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.
*
* @return bool TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
* FALSE otherwise.
*/
public function isInheritanceTypeTablePerClass()
{
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.
*/
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 $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.
* Assumes that the class names in the passed array are in the order:
* directParent -> directParentParent -> directParentParentParent ... -> root.
*
* @psalm-param list $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 $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']) && (count($overrideMapping) !== 1 || ! isset($overrideMapping['fetch']))) {
// TODO: Deprecate overriding the fetch mode via association override for 3.0,
// users should do this with a listener and a custom attribute/annotation
// TODO: Enable this exception in 2.8
//throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName);
//}
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 $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'])) {
// TODO: Enable this exception in 2.8
//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 => (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 $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 (strpos($table['name'], '.') !== false) {
[$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
{
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 $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 array $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 array $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 $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 $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 $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 (strpos($fieldName, '.')) {
[, $fieldName] = explode('.', $fieldName);
}
$resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
}
}
}
}
}
$this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
}
/**
* Adds a one-to-one mapping.
*
* @param array $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 $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 $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 $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 array $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|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
*/
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> $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|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
*/
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.
*
* @psalm-param array $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 string $name
* @param string $className
* @psalm-param class-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);
}
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 $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:
*
* array(
* 'sequenceName' => 'name',
* 'allocationSize' => 20,
* 'initialValue' => 1
* 'quoted' => 1
* )
*
*
* @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 $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 $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
*/
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>
*/
public function getAssociationsByTargetClass($targetClass)
{
$relations = [];
foreach ($this->associationMappings as $mapping) {
if ($mapping['targetEntity'] === $targetClass) {
$relations[$mapping['fieldName']] = $mapping;
}
}
return $relations;
}
/**
* @param string|null $className
* @psalm-param string|class-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 (strpos($className, '\\') === false && $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 $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();
}
}
$this->embeddedClasses[$mapping['fieldName']] = [
'class' => $this->fullyQualifiedClassName($mapping['class']),
'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 array $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()) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
return $reflectionProperty;
}
}
lib/Doctrine/ORM/Mapping/Column.php 0000644 00000005023 14227611130 0013054 0 ustar 00 |null */
public $enumType = null;
/** @var array */
public $options = [];
/** @var string|null */
public $columnDefinition;
/**
* @var string|null
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
* @Enum({"NEVER", "INSERT", "ALWAYS"})
*/
public $generated;
/**
* @param class-string<\BackedEnum>|null $enumType
* @param array $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;
}
}
lib/Doctrine/ORM/Mapping/ColumnResult.php 0000644 00000000723 14227611130 0014255 0 ustar 00 class = $class;
}
}
lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php 0000644 00000002610 14227611130 0017627 0 ustar 00 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();
}
}
lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php 0000644 00000003354 14227611130 0016065 0 ustar 00 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()));
}
}
lib/Doctrine/ORM/Mapping/DefaultQuoteStrategy.php 0000644 00000011616 14227611130 0015751 0 ustar 00 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);
}
}
lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php 0000644 00000001746 14227611130 0015614 0 ustar 00 name = $name;
$this->type = $type;
$this->length = $length;
$this->columnDefinition = $columnDefinition;
}
}
lib/Doctrine/ORM/Mapping/DiscriminatorMap.php 0000644 00000001175 14227611130 0015070 0 ustar 00
* @psalm-var array
*/
public $value;
/**
* @param array $value
* @psalm-param array $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
}
lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php 0000644 00000076554 14227611130 0016361 0 ustar 00
*/
protected $entityAnnotationClasses = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
assert($metadata instanceof Mapping\ClassMetadata);
$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 !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
$discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn);
$metadata->setDiscriminatorColumn(
[
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
]
);
} 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
* @psalm-param array $mapping
*/
private function loadRelationShipMapping(
ReflectionProperty $property,
array &$mapping,
ClassMetadata $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,
];
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.
*
* @psalm-return \Doctrine\ORM\Mapping\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 ClassMetadataInfo::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 callable[]
* @psalm-return list
*/
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
* }
*/
private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
{
return [
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
];
}
/**
* 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?: ClassMetadataInfo::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;
}
/**
* 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);
}
}
lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php 0000644 00000066634 14227611130 0016210 0 ustar 00 */
// @phpcs:ignore
protected $entityAnnotationClasses = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* @param array $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.'
));
}
parent::__construct(new AttributeReader(), $paths);
}
/**
* {@inheritDoc}
*/
public function isTransient($className)
{
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
foreach ($classAnnotations as $a) {
$annot = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
return false;
}
}
return true;
}
public function loadMetadataForClass($className, ClassMetadata $metadata): void
{
assert($metadata instanceof ClassMetadataInfo);
$reflectionClass = $metadata->getReflectionClass();
$classAttributes = $this->reader->getClassAnnotations($reflectionClass);
// Evaluate Entity annotation
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 annotation
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 annotation
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 !== Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
$metadata->setDiscriminatorColumn(
[
'name' => $discrColumnAttribute->name,
'type' => $discrColumnAttribute->type ?: 'string',
'length' => $discrColumnAttribute->length ?: 255,
'columnDefinition' => $discrColumnAttribute->columnDefinition,
]
);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate DiscriminatorMap annotation
if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
$discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
$metadata->setDiscriminatorMap($discrMapAttribute->value);
}
}
}
// Evaluate DoctrineChangeTrackingPolicy annotation
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 annotation
$cacheAttribute = $this->reader->getPropertyAnnotation($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 annotations
$joinColumns = [];
$joinColumnAttributes = $this->reader->getPropertyAnnotation($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->getPropertyAnnotation($property, Mapping\Column::class);
$oneToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class);
$oneToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class);
$manyToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class);
$manyToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class);
$embeddedAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class);
if ($columnAttribute !== null) {
$mapping = $this->columnToArray($property->getName(), $columnAttribute);
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
$generatedValueAttribute = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
if ($generatedValueAttribute !== null) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
}
if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
$seqGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class);
$customGeneratorAttribute = $this->reader->getPropertyAnnotation($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->getPropertyAnnotation($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->getPropertyAnnotation($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
}
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
$idAttribute = $this->reader->getPropertyAnnotation($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->getPropertyAnnotation($property, Mapping\JoinTable::class);
if ($joinTableAttribute !== null) {
$joinTable = [
'name' => $joinTableAttribute->name,
'schema' => $joinTableAttribute->schema,
];
}
foreach ($this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class) as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($this->reader->getPropertyAnnotation($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->getPropertyAnnotation($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(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate AttributeOverrides annotation
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 annotation
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 annotation
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 string $className The class name.
* @param string $fetchMode The fetch mode.
*
* @return int 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 callable[]
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$attributes = $this->reader->getMethodAnnotations($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
* }
*/
private function joinColumnToArray($joinColumn): array
{
return [
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
];
}
/**
* 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;
}
}
lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php 0000644 00000007357 14227611130 0016154 0 ustar 00 */
private array $isRepeatableAttribute = [];
/** @return array */
public function getClassAnnotations(ReflectionClass $class): array
{
return $this->convertToAttributeInstances($class->getAttributes());
}
/** @return Annotation|RepeatableAttributeCollection|null */
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
return $this->getClassAnnotations($class)[$annotationName]
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
}
/** @return array */
public function getMethodAnnotations(ReflectionMethod $method): array
{
return $this->convertToAttributeInstances($method->getAttributes());
}
/** @return Annotation|RepeatableAttributeCollection|null */
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
return $this->getMethodAnnotations($method)[$annotationName]
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
}
/** @return array */
public function getPropertyAnnotations(ReflectionProperty $property): array
{
return $this->convertToAttributeInstances($property->getAttributes());
}
/** @return Annotation|RepeatableAttributeCollection|null */
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
return $this->getPropertyAnnotations($property)[$annotationName]
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
}
/**
* @param array $attributes
*
* @return array
*/
private function convertToAttributeInstances(array $attributes): array
{
$instances = [];
foreach ($attributes as $attribute) {
$attributeName = $attribute->getName();
assert(is_string($attributeName));
// Make sure we only get Doctrine Annotations
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;
}
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;
}
}
lib/Doctrine/ORM/Mapping/Driver/DatabaseDriver.php 0000644 00000043022 14227611130 0015733 0 ustar 00 |null */
private $tables = null;
/** @var mixed[] */
private $classToTableNames = [];
/** @psalm-var array */
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 $entityTables
* @psalm-param list $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}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$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;
}
$tables = [];
foreach ($this->_sm->listTableNames() as $tableName) {
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($tables as $tableName => $table) {
$foreignKeys = $this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()
? $table->getForeignKeys()
: [];
$allForeignKeyColumns = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
if (! $table->hasPrimaryKey()) {
throw new MappingException(
'Table ' . $table->getName() . ' has no primary key. Doctrine does not ' .
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $table->getPrimaryKey()->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->getTableForeignKeys($this->tables[$tableName]);
$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(ClassMetadataInfo::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' => $column->getType()->getName(),
'nullable' => ! $column->getNotnull(),
];
// Type specific elements
switch ($fieldMapping['type']) {
case Types::ARRAY:
case Types::BLOB:
case Types::GUID:
case self::JSON_ARRAY:
case Types::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)
{
$tableName = $metadata->table['name'];
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->getTableForeignKeys($this->tables[$tableName]);
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 foreign keys.
*
* @return ForeignKeyConstraint[]
* @psalm-return array
*/
private function getTableForeignKeys(Table $table): array
{
return $this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()
? $table->getForeignKeys()
: [];
}
/**
* 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);
}
}
lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php 0000644 00000004521 14227611130 0017041 0 ustar 00
*/
final class RepeatableAttributeCollection extends ArrayObject
{
}
lib/Doctrine/ORM/Mapping/Driver/SimplifiedXmlDriver.php 0000644 00000001124 14227611130 0016772 0 ustar 00 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 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) {
//
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;
}
//
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 !== Metadata::INHERITANCE_TYPE_NONE) {
// Evaluate
if (isset($xmlRoot->{'discriminator-column'})) {
$discrColumn = $xmlRoot->{'discriminator-column'};
$metadata->setDiscriminatorColumn(
[
'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null,
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (string) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null,
]
);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate
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
if (isset($xmlRoot['change-tracking-policy'])) {
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
. strtoupper((string) $xmlRoot['change-tracking-policy'])));
}
// Evaluate
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
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 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 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'] = (string) $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 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 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(' 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 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 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'];
}
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(' 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->{'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(Metadata::class . '::FETCH_' . (string) $overrideElement['fetch']);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate
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|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
* }
*/
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'];
}
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
*/
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 both YAML and Annotation 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)
{
$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));
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;
}
/**
* @param mixed $element
*
* @return bool
*/
protected function evaluateBoolean($element)
{
$flag = (string) $element;
return $flag === 'true' || $flag === '1';
}
}
lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php 0000644 00000106413 14227611130 0015135 0 ustar 00 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 !== Metadata::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']) ? (string) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string) $discrColumn['columnDefinition'] : 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(Metadata::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: mixed, region: (string|null)} $cacheMapping
* @psalm-param array{usage: string, region?: string} $cacheMapping
*
* @return mixed[]
* @psalm-return array{usage: int, 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));
}
}
lib/Doctrine/ORM/Mapping/Embeddable.php 0000644 00000000324 14227611130 0013622 0 ustar 00 class = $class;
$this->columnPrefix = $columnPrefix;
}
}
lib/Doctrine/ORM/Mapping/Entity.php 0000644 00000001444 14227611130 0013076 0 ustar 00 |null
*/
public $repositoryClass;
/** @var bool */
public $readOnly = false;
/**
* @psalm-param class-string|null $repositoryClass
*/
public function __construct(?string $repositoryClass = null, bool $readOnly = false)
{
$this->repositoryClass = $repositoryClass;
$this->readOnly = $readOnly;
}
}
lib/Doctrine/ORM/Mapping/EntityListenerResolver.php 0000644 00000001533 14227611130 0016325 0 ustar 00
*/
public $value = [];
/**
* @param array $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
}
}
lib/Doctrine/ORM/Mapping/EntityResult.php 0000644 00000001755 14227611130 0014302 0 ustar 00
*/
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;
}
lib/Doctrine/ORM/Mapping/Exception/CannotGenerateIds.php 0000644 00000000773 14227611130 0017121 0 ustar 00 strategy = $strategy;
}
}
lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php 0000644 00000000337 14227611130 0015755 0 ustar 00 |null */
public $columns;
/** @var array|null */
public $fields;
/** @var array|null */
public $flags;
/** @var array|null */
public $options;
/**
* @param array|null $columns
* @param array|null $fields
* @param array|null $flags
* @param array|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;
}
}
lib/Doctrine/ORM/Mapping/InheritanceType.php 0000644 00000001133 14227611130 0014710 0 ustar 00 value = $value;
}
}
lib/Doctrine/ORM/Mapping/InverseJoinColumn.php 0000644 00000002425 14227611130 0015233 0 ustar 00 name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
}
}
lib/Doctrine/ORM/Mapping/JoinColumn.php 0000644 00000002656 14227611130 0013705 0 ustar 00 name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
}
}
lib/Doctrine/ORM/Mapping/JoinColumns.php 0000644 00000000355 14227611130 0014062 0 ustar 00 */
public $value;
}
lib/Doctrine/ORM/Mapping/JoinTable.php 0000644 00000002137 14227611130 0013471 0 ustar 00 */
public $joinColumns = [];
/** @var array<\Doctrine\ORM\Mapping\JoinColumn> */
public $inverseJoinColumns = [];
public function __construct(
?string $name = null,
?string $schema = null,
$joinColumns = [],
$inverseJoinColumns = []
) {
$this->name = $name;
$this->schema = $schema;
$this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns;
$this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn
? [$inverseJoinColumns]
: $inverseJoinColumns;
}
}
lib/Doctrine/ORM/Mapping/ManyToMany.php 0000644 00000003405 14227611130 0013655 0 ustar 00 targetEntity = $targetEntity;
$this->mappedBy = $mappedBy;
$this->inversedBy = $inversedBy;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->orphanRemoval = $orphanRemoval;
$this->indexBy = $indexBy;
}
}
lib/Doctrine/ORM/Mapping/ManyToOne.php 0000644 00000002126 14227611130 0013471 0 ustar 00 targetEntity = $targetEntity;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->inversedBy = $inversedBy;
}
}
lib/Doctrine/ORM/Mapping/MappedSuperclass.php 0000644 00000001266 14227611130 0015077 0 ustar 00 |null
*/
public $repositoryClass;
/**
* @psalm-param class-string|null $repositoryClass
*/
public function __construct(?string $repositoryClass = null)
{
$this->repositoryClass = $repositoryClass;
}
}
lib/Doctrine/ORM/Mapping/MappingException.php 0000644 00000070760 14227611130 0015103 0 ustar 00 $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 string $className
* @param string $annotation
*
* @return MappingException
*/
public static function invalidFetchMode($className, $annotation)
{
return new self("Entity '" . $className . "' has a mapping with invalid fetch mode '" . $annotation . "'");
}
public static function invalidGeneratedMode(string $annotation): MappingException
{
return new self("Invalid generated mode '" . $annotation . "'");
}
/**
* @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(
'Override for %s::%s is only allowed for attributes/associations ' .
'declared on a mapped superclass or a trait.',
$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 $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);
}
}
lib/Doctrine/ORM/Mapping/NamedNativeQueries.php 0000644 00000000726 14227611130 0015355 0 ustar 00
*/
public $value = [];
}
lib/Doctrine/ORM/Mapping/NamedNativeQuery.php 0000644 00000001434 14227611130 0015042 0 ustar 00 */
public $value;
}
lib/Doctrine/ORM/Mapping/NamedQuery.php 0000644 00000000367 14227611130 0013677 0 ustar 00 */
public $cascade;
/**
* The fetching strategy to use for the association.
*
* @var string
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch = 'LAZY';
/** @var bool */
public $orphanRemoval = false;
/** @var string */
public $indexBy;
/**
* @param class-string|null $targetEntity
* @param string[]|null $cascade
*/
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;
}
}
lib/Doctrine/ORM/Mapping/OneToOne.php 0000644 00000002554 14227611130 0013313 0 ustar 00 |null */
public $cascade;
/**
* The fetching strategy to use for the association.
*
* @var string
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch = 'LAZY';
/** @var bool */
public $orphanRemoval = false;
/**
* @param class-string|null $targetEntity
* @param array|null $cascade
*/
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;
}
}
lib/Doctrine/ORM/Mapping/OrderBy.php 0000644 00000000771 14227611130 0013172 0 ustar 00 */
public $value;
/**
* @param array $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
}
lib/Doctrine/ORM/Mapping/PostLoad.php 0000644 00000000324 14227611130 0013343 0 ustar 00
*/
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);
}
lib/Doctrine/ORM/Mapping/Reflection/ReflectionPropertiesGetter.php 0000644 00000007515 14227611130 0021243 0 ustar 00 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] = call_user_func_array(
'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>
*/
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
*/
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;
}
}
lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php 0000644 00000004363 14227611130 0017076 0 ustar 00 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);
}
}
lib/Doctrine/ORM/Mapping/ReflectionEnumProperty.php 0000644 00000004064 14227611130 0016307 0 ustar 00 */
private $enumType;
/**
* @param class-string $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|null
*/
#[ReturnTypeWillChange]
public function getValue($object = null)
{
if ($object === null) {
return null;
}
$enum = $this->originalReflectionProperty->getValue($object);
if ($enum === null) {
return null;
}
return $enum->value;
}
/**
* @param object $object
* @param mixed $value
*/
public function setValue($object, $value = null): void
{
if ($value !== null) {
$enumType = $this->enumType;
try {
$value = $enumType::from($value);
} catch (ValueError $e) {
assert(is_string($value) || is_int($value));
throw MappingException::invalidEnumValue(
get_class($object),
$this->originalReflectionProperty->getName(),
(string) $value,
$enumType,
$e
);
}
}
$this->originalReflectionProperty->setValue($object, $value);
}
}
lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php 0000644 00000002504 14227611130 0017155 0 ustar 00 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));
}
}
}
lib/Doctrine/ORM/Mapping/SequenceGenerator.php 0000644 00000001402 14227611130 0015233 0 ustar 00 sequenceName = $sequenceName;
$this->allocationSize = $allocationSize;
$this->initialValue = $initialValue;
}
}
lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php 0000644 00000001527 14227611130 0015552 0 ustar 00
*/
public $entities = [];
/**
* Specifies the result set mapping to scalar values.
*
* @var array<\Doctrine\ORM\Mapping\ColumnResult>
*/
public $columns = [];
}
lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php 0000644 00000000720 14227611130 0015727 0 ustar 00
*/
public $value = [];
}
lib/Doctrine/ORM/Mapping/Table.php 0000644 00000002444 14227611130 0012652 0 ustar 00 |null */
public $indexes;
/** @var array<\Doctrine\ORM\Mapping\UniqueConstraint>|null */
public $uniqueConstraints;
/** @var array */
public $options = [];
/**
* @param array<\Doctrine\ORM\Mapping\Index> $indexes
* @param array<\Doctrine\ORM\Mapping\UniqueConstraint> $uniqueConstraints
* @param array $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;
}
}
lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php 0000644 00000007075 14227611130 0016616 0 ustar 00 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 (strpos($className, '\\') !== false) {
$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}
*/
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);
}
}
lib/Doctrine/ORM/Mapping/UniqueConstraint.php 0000644 00000002000 14227611130 0015122 0 ustar 00 |null */
public $columns;
/** @var array|null */
public $fields;
/** @var array|null */
public $options;
/**
* @param array $columns
* @param array $fields
* @param array $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;
}
}
lib/Doctrine/ORM/Mapping/Version.php 0000644 00000000327 14227611130 0013246 0 ustar 00 sql = $sql;
return $this;
}
/**
* Gets the SQL query.
*
* @return mixed The built SQL query or an array of all SQL queries.
*
* @override
*/
public function getSQL()
{
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
);
}
}
lib/Doctrine/ORM/NoResultException.php 0000644 00000000544 14227611130 0013661 0 ustar 00 $associationMapping
*
* @return ORMInvalidArgumentException
*/
public static function newEntityFoundThroughRelationship(array $associationMapping, $entry)
{
return new self(self::newEntityFoundThroughRelationshipMessage($associationMapping, $entry));
}
/**
* @param object $entry
* @psalm-param array $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()
{
return new self('Binding entities to query parameters only allowed for entities that have an identifier.');
}
/**
* @param mixed[] $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
*
* @param mixed $entityName The given entity (which shouldn't be a string)
*
* @return self
*/
public static function invalidEntityName($entityName)
{
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 array $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.'
);
}
}
lib/Doctrine/ORM/OptimisticLockException.php 0000644 00000003743 14227611130 0015047 0 ustar 00 entity = $entity;
}
/**
* Gets the entity that caused the exception.
*
* @return object|null
*/
public function getEntity()
{
return $this->entity;
}
/**
* @param object $entity
*
* @return OptimisticLockException
*/
public static function lockFailed($entity)
{
return new self('The optimistic lock on an entity failed.', $entity);
}
/**
* @param object $entity
* @param int|DateTimeInterface $expectedLockVersion
* @param int|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);
}
}
lib/Doctrine/ORM/PersistentCollection.php 0000644 00000050401 14227611130 0014400 0 ustar 00
*/
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
*/
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 array|null
*/
private $association;
/**
* The EntityManager that manages the persistence of the collection.
*
* @var EntityManagerInterface
*/
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
*/
private $backRefFieldName;
/**
* The class descriptor of the collection's entity type.
*
* @var ClassMetadata
*/
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 $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 array $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
{
return $this->typeClass;
}
/**
* 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->collection->add($element);
// If _backRefFieldName is set and its a one-to-many association,
// we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
// Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName]->setValue(
$element,
$this->owner
);
$this->em->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->collection->set($key, $element);
// If _backRefFieldName is set, then the association is bidirectional
// and we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
// 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->collection->toArray();
$this->isDirty = false;
}
/**
* INTERNAL:
* Returns the last snapshot of the elements in the collection.
*
* @psalm-return array The last snapshot of the elements.
*/
public function getSnapshot(): array
{
return $this->snapshot;
}
/**
* INTERNAL:
* getDeleteDiff
*
* @return mixed[]
*/
public function getDeleteDiff(): array
{
$collectionItems = $this->collection->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->collection->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 array|null
*/
public function getMapping(): ?array
{
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->association['isOwningSide'] &&
$this->association['type'] === ClassMetadata::MANY_TO_MANY &&
$this->owner &&
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()
) {
$this->em->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}
*
* @return object
*/
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->association['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']
) {
$this->em->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->association['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']
) {
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
}
return $removed;
}
/**
* {@inheritdoc}
*/
public function containsKey($key): bool
{
if (
! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])
) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $this->collection->containsKey($key) || $persister->containsKey($this, $key);
}
return parent::containsKey($key);
}
/**
* {@inheritdoc}
*/
public function contains($element): bool
{
if (! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $this->collection->contains($element) || $persister->contains($this, $element);
}
return parent::contains($element);
}
/**
* {@inheritdoc}
*/
public function get($key)
{
if (
! $this->initialized
&& $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])
) {
if (! $this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) {
return $this->em->find($this->typeClass->name, $key);
}
return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
}
return parent::get($key);
}
public function count(): int
{
if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $persister->count($this) + ($this->isDirty ? $this->collection->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->em->getUnitOfWork()->cancelOrphanRemoval($value);
}
}
/**
* {@inheritdoc}
*/
public function add($value): bool
{
$this->collection->add($value);
$this->changed();
if (is_object($value) && $this->em) {
$this->em->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->collection->isEmpty() && $this->count() === 0;
}
public function clear(): void
{
if ($this->initialized && $this->isEmpty()) {
$this->collection->clear();
return;
}
$uow = $this->em->getUnitOfWork();
if (
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->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->collection as $element) {
$uow->scheduleOrphanRemoval($element);
}
}
$this->collection->clear();
$this->initialized = true; // direct call, {@link initialize()} is too expensive
if ($this->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
*/
public function slice($offset, $length = null): array
{
if (! $this->initialized && ! $this->isDirty && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
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
*
* @throws RuntimeException
*/
public function matching(Criteria $criteria): Collection
{
if ($this->isDirty) {
$this->initialize();
}
if ($this->initialized) {
return $this->collection->matching($criteria);
}
if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->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() ?: $this->association['orderBy'] ?? []);
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
return $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
? new LazyCriteriaCollection($persister, $criteria)
: new ArrayCollection($persister->loadCriteria($criteria));
}
/**
* Retrieves the wrapped Collection instance.
*
* @return Collection
*/
public function unwrap(): Collection
{
return $this->collection;
}
protected function doInitialize(): void
{
// Has NEW objects added through add(). Remember them.
$newlyAddedDirtyObjects = [];
if ($this->isDirty) {
$newlyAddedDirtyObjects = $this->collection->toArray();
}
$this->collection->clear();
$this->em->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->collection->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->collection, 'add']);
$this->isDirty = true;
}
}
}
lib/Doctrine/ORM/Persisters/Collection/AbstractCollectionPersister.php 0000644 00000003451 14227611130 0022145 0 ustar 00 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));
}
}
lib/Doctrine/ORM/Persisters/Collection/CollectionPersister.php 0000644 00000003474 14227611130 0020466 0 ustar 00 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);
$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 array $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 array $mapping
*
* @return string[]
* @psalm-return list
*/
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
*/
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